Dark mod tüm komponentlere eklendi
This commit is contained in:
parent
549bb1aa76
commit
08a2297a66
56 changed files with 782 additions and 697 deletions
|
|
@ -18188,6 +18188,12 @@
|
||||||
"en": "Data Source",
|
"en": "Data Source",
|
||||||
"tr": "Veri Kaynağı"
|
"tr": "Veri Kaynağı"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"resourceName": "Platform",
|
||||||
|
"key": "App.DeveloperKit.CrudEndpoints.DataSourceDescription",
|
||||||
|
"en": "Data source of the CRUD endpoints",
|
||||||
|
"tr": "CRUD endpointlerinin veri kaynağı"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"resourceName": "Platform",
|
"resourceName": "Platform",
|
||||||
"key": "App.DeveloperKit.CrudEndpoints.Loading",
|
"key": "App.DeveloperKit.CrudEndpoints.Loading",
|
||||||
|
|
|
||||||
|
|
@ -226,7 +226,6 @@ public class QueryManager : PlatformDomainService, IQueryManager
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
sql = $"DELETE FROM \"{listForm.SelectCommand}\" WHERE {where}";
|
sql = $"DELETE FROM \"{listForm.SelectCommand}\" WHERE {where}";
|
||||||
Console.WriteLine(sql);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -349,7 +349,7 @@
|
||||||
"CustomComponents": [
|
"CustomComponents": [
|
||||||
{
|
{
|
||||||
"name": "DynamicEntityComponent",
|
"name": "DynamicEntityComponent",
|
||||||
"code": "import React, { useEffect, useState } from \"react\";\nimport axios from \"axios\";\n\ninterface DynamicEntityComponentProps {\n title: string;\n}\n\nconst api = axios.create({\n baseURL: \"https://localhost:44344\", // defaults'ı her seferinde set etme\n});\n\nconst DynamicEntityComponent: React.FC<DynamicEntityComponentProps> = ({ title }) => {\n const [data, setData] = useState<Array<{ id: string; name: string }>>([]);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n\n useEffect(() => {\n const fetchData = async () => {\n setLoading(true);\n setError(null);\n\n try {\n const res = await api.get(`/api/app/crudendpoint/${title}`);\n const raw = Array.isArray(res.data) ? res.data : res.data?.items ?? [];\n\n const filtered = raw.map((item: any) => ({\n id: item.Id ?? item.id,\n name: item.Name ?? item.name,\n }));\n\n setData(filtered);\n } catch (err: any) {\n setError(err.message || \"Failed to fetch data\");\n } finally {\n setLoading(false);\n }\n };\n\n if (title) fetchData();\n }, [title]);\n\n if (loading) return <div>Loading...</div>;\n if (error) return <div className=\"text-red-600\">Error: {error}</div>;\n if (!data.length) return <div>No records found</div>;\n\n const headers = [\"id\", \"name\", \"actions\"];\n\n return (\n <div className=\"overflow-auto\">\n <table className=\"min-w-full bg-white border border-slate-200 shadow-sm rounded-lg\">\n <thead className=\"bg-slate-100\">\n <tr>\n {headers.map((key) => (\n <th\n key={key}\n className=\"text-left px-4 py-2 border-b border-slate-200 text-sm font-medium text-slate-700\"\n >\n {key === \"actions\" ? \"Actions\" : key}\n </th>\n ))}\n </tr>\n </thead>\n <tbody>\n {data.map((item, rowIndex) => (\n <tr key={rowIndex} className=\"hover:bg-slate-50\">\n <td className=\"px-4 py-2 border-b border-slate-100 text-sm text-slate-800\">\n {item.id}\n </td>\n <td className=\"px-4 py-2 border-b border-slate-100 text-sm text-slate-800\">\n {item.name}\n </td>\n <td className=\"px-4 py-2 border-b border-slate-100\">\n <button\n type=\"button\"\n onClick={() => alert(item.name)}\n className=\"bg-blue-600 hover:bg-blue-700 text-white text-sm px-3 py-1 rounded-lg shadow-sm transition\"\n >\n Show Name\n </button>\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n );\n};\n\nexport default DynamicEntityComponent;",
|
"code": "import React, { useEffect, useState } from \"react\";\nimport axios from \"axios\";\n\ninterface DynamicEntityComponentProps {\n title: string;\n}\n\nconst api = axios.create({\n baseURL: \"https://localhost:44344\",\n});\n\nconst DynamicEntityComponent: React.FC<DynamicEntityComponentProps> = ({ title }) => {\n const [data, setData] = useState<Array<{ id: string; name: string }>>([]);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n\n useEffect(() => {\n const fetchData = async () => {\n setLoading(true);\n setError(null);\n\n try {\n const res = await api.get(`/api/app/crudendpoint/${title}`);\n const raw = Array.isArray(res.data) ? res.data : res.data?.items ?? [];\n\n const filtered = raw.map((item: any) => ({\n id: item.Id ?? item.id,\n name: item.Name ?? item.name,\n }));\n\n setData(filtered);\n } catch (err: any) {\n setError(err.message || \"Failed to fetch data\");\n } finally {\n setLoading(false);\n }\n };\n\n if (title) fetchData();\n }, [title]);\n\n if (loading) return <div>Loading...</div>;\n if (error) return <div className=\"text-red-600 dark:text-red-400\">Error: {error}</div>;\n if (!data.length) return <div>No records found</div>;\n\n const headers = [\"id\", \"name\", \"actions\"];\n\n return (\n <div className=\"overflow-auto\">\n <table className=\"min-w-full bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-700 shadow-sm rounded-lg\">\n <thead className=\"bg-slate-100 dark:bg-slate-800\">\n <tr>\n {headers.map((key) => (\n <th\n key={key}\n className=\"text-left px-4 py-2 border-b border-slate-200 dark:border-slate-700 text-sm font-medium text-slate-700 dark:text-slate-200\"\n >\n {key === \"actions\" ? \"Actions\" : key}\n </th>\n ))}\n </tr>\n </thead>\n <tbody>\n {data.map((item, rowIndex) => (\n <tr key={rowIndex} className=\"hover:bg-slate-50 dark:hover:bg-slate-800\">\n <td className=\"px-4 py-2 border-b border-slate-100 dark:border-slate-800 text-sm text-slate-800 dark:text-slate-100\">\n {item.id}\n </td>\n <td className=\"px-4 py-2 border-b border-slate-100 dark:border-slate-800 text-sm text-slate-800 dark:text-slate-100\">\n {item.name}\n </td>\n <td className=\"px-4 py-2 border-b border-slate-100 dark:border-slate-800\">\n <button\n type=\"button\"\n onClick={() => alert(item.name)}\n className=\"bg-blue-600 hover:bg-blue-700 dark:bg-blue-800 dark:hover:bg-blue-900 text-white text-sm px-3 py-1 rounded-lg shadow-sm transition\"\n >\n Show Name\n </button>\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n );\n};\n\nexport default DynamicEntityComponent;",
|
||||||
"props": null,
|
"props": null,
|
||||||
"description": null,
|
"description": null,
|
||||||
"isActive": true,
|
"isActive": true,
|
||||||
|
|
|
||||||
|
|
@ -16,16 +16,16 @@ const ComponentSelector: React.FC<ComponentSelectorProps> = ({
|
||||||
onRefresh
|
onRefresh
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<div className="p-4 bg-white border-b">
|
<div className="p-4 bg-white dark:bg-gray-900 border-b border-gray-200 dark:border-gray-700">
|
||||||
<div className="flex items-center justify-between mb-2">
|
<div className="flex items-center justify-between mb-2">
|
||||||
<label className="block text-sm font-medium text-gray-700">
|
<label className="block text-sm font-medium text-gray-700 dark:text-gray-200">
|
||||||
Select Component
|
Select Component
|
||||||
</label>
|
</label>
|
||||||
<Button
|
<Button
|
||||||
variant='solid'
|
variant='solid'
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={onRefresh}
|
onClick={onRefresh}
|
||||||
className="px-3 py-1 bg-blue-500 text-white text-xs rounded hover:bg-blue-600 transition-colors"
|
className="px-3 py-1 bg-blue-500 text-white text-xs rounded hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-700 dark:text-white transition-colors"
|
||||||
title="Refresh component list"
|
title="Refresh component list"
|
||||||
>
|
>
|
||||||
Refresh
|
Refresh
|
||||||
|
|
@ -34,7 +34,7 @@ const ComponentSelector: React.FC<ComponentSelectorProps> = ({
|
||||||
<select
|
<select
|
||||||
value={selectedComponentId || ''}
|
value={selectedComponentId || ''}
|
||||||
onChange={(e) => onSelectComponent(e.target.value || null)}
|
onChange={(e) => onSelectComponent(e.target.value || null)}
|
||||||
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-800 dark:text-white dark:placeholder-gray-400"
|
||||||
>
|
>
|
||||||
<option value="">No component selected</option>
|
<option value="">No component selected</option>
|
||||||
{components.map(component => (
|
{components.map(component => (
|
||||||
|
|
|
||||||
|
|
@ -31,33 +31,33 @@ export const PanelManager: React.FC<PanelManagerProps> = ({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed inset-0 bg-black bg-opacity-50 z-50 flex items-center justify-center">
|
<div className="fixed inset-0 bg-black bg-opacity-50 z-50 flex items-center justify-center">
|
||||||
<div className="bg-white rounded-lg shadow-xl w-96 max-w-full mx-4">
|
<div className="bg-white dark:bg-gray-900 rounded-lg shadow-xl w-96 max-w-full mx-4">
|
||||||
<div className="flex items-center justify-between p-4 border-b border-gray-200">
|
<div className="flex items-center justify-between p-4 border-b border-gray-200 dark:border-gray-700">
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
<FaBars className="w-5 h-5 text-blue-600" />
|
<FaBars className="w-5 h-5 text-blue-600 dark:text-blue-400" />
|
||||||
<h2 className="text-base font-semibold text-gray-900">Panel Manager</h2>
|
<h2 className="text-base font-semibold text-gray-900 dark:text-gray-100">Panel Manager</h2>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
className="p-1 hover:bg-gray-100 rounded transition-colors"
|
className="p-1 hover:bg-gray-100 dark:hover:bg-gray-800 rounded transition-colors"
|
||||||
title="Kapat"
|
title="Kapat"
|
||||||
>
|
>
|
||||||
<FaTimes className="w-5 h-5 text-gray-500" />
|
<FaTimes className="w-5 h-5 text-gray-500 dark:text-gray-400" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div className="p-4">
|
<div className="p-4">
|
||||||
<p className="text-sm text-gray-600 mb-4">Customize Workspace</p>
|
<p className="text-sm text-gray-600 dark:text-gray-300 mb-4">Customize Workspace</p>
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<h3 className="text-sm font-medium text-gray-900">Panels</h3>
|
<h3 className="text-sm font-medium text-gray-900 dark:text-gray-100">Panels</h3>
|
||||||
{paneller.map(({ key, label, icon: Icon }) => (
|
{paneller.map(({ key, label, icon: Icon }) => (
|
||||||
<div key={key} className="flex items-center justify-between p-3 bg-gray-50 rounded-lg">
|
<div key={key} className="flex items-center justify-between p-3 bg-gray-50 dark:bg-gray-800 rounded-lg">
|
||||||
<div className="flex items-center space-x-3">
|
<div className="flex items-center space-x-3">
|
||||||
<Icon className="w-4 h-4 text-gray-600" />
|
<Icon className="w-4 h-4 text-gray-600 dark:text-gray-300" />
|
||||||
<span className="text-sm font-medium text-gray-700">{label}</span>
|
<span className="text-sm font-medium text-gray-700 dark:text-gray-200">{label}</span>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
onClick={() => onPanelToggle(key)}
|
onClick={() => onPanelToggle(key)}
|
||||||
className={`p-1 rounded transition-colors ${panelState[key] ? "text-blue-600 hover:bg-blue-100" : "text-gray-400 hover:bg-gray-200"}`}
|
className={`p-1 rounded transition-colors ${panelState[key] ? "text-blue-600 dark:text-blue-400 hover:bg-blue-100 dark:hover:bg-blue-900" : "text-gray-400 dark:text-gray-500 hover:bg-gray-200 dark:hover:bg-gray-700"}`}
|
||||||
title={panelState[key] ? "Hide" : "Show"}
|
title={panelState[key] ? "Hide" : "Show"}
|
||||||
>
|
>
|
||||||
{panelState[key] ? <FaEye className="w-4 h-4" /> : <FaEyeSlash className="w-4 h-4" />}
|
{panelState[key] ? <FaEye className="w-4 h-4" /> : <FaEyeSlash className="w-4 h-4" />}
|
||||||
|
|
|
||||||
|
|
@ -228,10 +228,10 @@ const PropertyPanel: React.FC<PropertyPanelProps> = ({
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<div key={property.name} className="mb-4">
|
<div key={property.name} className="mb-4">
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
<label className="block text-sm font-medium text-gray-700 dark:text-gray-200 mb-2">
|
||||||
{property.name}
|
{property.name}
|
||||||
{property.description && (
|
{property.description && (
|
||||||
<span className="text-gray-500 text-xs ml-1">
|
<span className="text-gray-500 dark:text-gray-400 text-xs ml-1">
|
||||||
({property.description})
|
({property.description})
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
|
@ -251,7 +251,7 @@ const PropertyPanel: React.FC<PropertyPanelProps> = ({
|
||||||
}
|
}
|
||||||
className="mr-2"
|
className="mr-2"
|
||||||
/>
|
/>
|
||||||
<span className="text-sm text-gray-600">{property.name}</span>
|
<span className="text-sm text-gray-600 dark:text-gray-300">{property.name}</span>
|
||||||
</label>
|
</label>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
@ -261,7 +261,7 @@ const PropertyPanel: React.FC<PropertyPanelProps> = ({
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
handleLocalPropertyChange(property.name, e.target.value)
|
handleLocalPropertyChange(property.name, e.target.value)
|
||||||
}
|
}
|
||||||
className="flex-1 px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
className="flex-1 px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-800 dark:text-white dark:placeholder-gray-400"
|
||||||
>
|
>
|
||||||
<option value="">Select {property.name}</option>
|
<option value="">Select {property.name}</option>
|
||||||
{property.options.map((option) => (
|
{property.options.map((option) => (
|
||||||
|
|
@ -280,13 +280,13 @@ const PropertyPanel: React.FC<PropertyPanelProps> = ({
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
handleLocalPropertyChange(property.name, e.target.value)
|
handleLocalPropertyChange(property.name, e.target.value)
|
||||||
}
|
}
|
||||||
className="flex-1 px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
className="flex-1 px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-800 dark:text-white dark:placeholder-gray-400"
|
||||||
placeholder={`Enter ${property.name}`}
|
placeholder={`Enter ${property.name}`}
|
||||||
/>
|
/>
|
||||||
{isTailwindProperty && (
|
{isTailwindProperty && (
|
||||||
<button
|
<button
|
||||||
onClick={() => openTailwindModal(property.name)}
|
onClick={() => openTailwindModal(property.name)}
|
||||||
className="px-3 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600 transition-colors text-sm"
|
className="px-3 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-700 dark:text-white transition-colors text-sm"
|
||||||
title="Select Tailwind Classes"
|
title="Select Tailwind Classes"
|
||||||
>
|
>
|
||||||
TW
|
TW
|
||||||
|
|
@ -299,7 +299,7 @@ const PropertyPanel: React.FC<PropertyPanelProps> = ({
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
handleLocalPropertyChange(property.name, e.target.value)
|
handleLocalPropertyChange(property.name, e.target.value)
|
||||||
}
|
}
|
||||||
className="w-10 h-10 border border-gray-300 rounded-md"
|
className="w-10 h-10 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-800"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
|
@ -313,13 +313,13 @@ const PropertyPanel: React.FC<PropertyPanelProps> = ({
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
handleLocalPropertyChange(property.name, e.target.value)
|
handleLocalPropertyChange(property.name, e.target.value)
|
||||||
}
|
}
|
||||||
className="flex-1 px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
className="flex-1 px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-800 dark:text-white dark:placeholder-gray-400"
|
||||||
placeholder={`Enter ${property.name}`}
|
placeholder={`Enter ${property.name}`}
|
||||||
/>
|
/>
|
||||||
{isTailwindProperty && (
|
{isTailwindProperty && (
|
||||||
<button
|
<button
|
||||||
onClick={() => openTailwindModal(property.name)}
|
onClick={() => openTailwindModal(property.name)}
|
||||||
className="px-3 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600 transition-colors text-sm"
|
className="px-3 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-700 dark:text-white transition-colors text-sm"
|
||||||
title="Select Tailwind Classes"
|
title="Select Tailwind Classes"
|
||||||
>
|
>
|
||||||
TW
|
TW
|
||||||
|
|
@ -332,7 +332,7 @@ const PropertyPanel: React.FC<PropertyPanelProps> = ({
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
handleLocalPropertyChange(property.name, e.target.value)
|
handleLocalPropertyChange(property.name, e.target.value)
|
||||||
}
|
}
|
||||||
className="w-10 h-10 border border-gray-300 rounded-md"
|
className="w-10 h-10 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-800"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
|
@ -345,14 +345,14 @@ const PropertyPanel: React.FC<PropertyPanelProps> = ({
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
handleLocalPropertyChange(property.name, Number(e.target.value))
|
handleLocalPropertyChange(property.name, Number(e.target.value))
|
||||||
}
|
}
|
||||||
className="flex-1 px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
className="flex-1 px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-800 dark:text-white dark:placeholder-gray-400"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{property.type === "array" && (
|
{property.type === "array" && (
|
||||||
<>
|
<>
|
||||||
<textarea
|
<textarea
|
||||||
className="flex-1 px-3 py-2 border border-gray-300 rounded-md font-mono text-xs focus:outline-none focus:ring-2 focus:ring-blue-500"
|
className="flex-1 px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md font-mono text-xs focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-800 dark:text-white dark:placeholder-gray-400"
|
||||||
rows={Math.max(3, arrayInputValue.split('\n').length)}
|
rows={Math.max(3, arrayInputValue.split('\n').length)}
|
||||||
value={arrayInputValue}
|
value={arrayInputValue}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
|
|
@ -406,11 +406,11 @@ const PropertyPanel: React.FC<PropertyPanelProps> = ({
|
||||||
|
|
||||||
if (!selectedComponent) {
|
if (!selectedComponent) {
|
||||||
return (
|
return (
|
||||||
<div className="h-full bg-gray-50 p-4">
|
<div className="h-full bg-gray-50 dark:bg-gray-900 p-4">
|
||||||
<div className="text-center text-gray-500 mt-8">
|
<div className="text-center text-gray-500 dark:text-gray-400 mt-8">
|
||||||
<div className="text-4xl mb-4">🎯</div>
|
<div className="text-4xl mb-4">🎯</div>
|
||||||
<h3 className="text-lg font-medium mb-2">No Component Selected</h3>
|
<h3 className="text-lg font-medium mb-2 text-gray-700 dark:text-gray-200">No Component Selected</h3>
|
||||||
<p className="text-sm">
|
<p className="text-sm text-gray-500 dark:text-gray-400">
|
||||||
Select a component from the editor to edit its properties
|
Select a component from the editor to edit its properties
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -480,7 +480,7 @@ const PropertyPanel: React.FC<PropertyPanelProps> = ({
|
||||||
return (
|
return (
|
||||||
<div className="w-full text-white flex flex-col h-full">
|
<div className="w-full text-white flex flex-col h-full">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="border-b bg-gray-50 flex items-center justify-between">
|
<div className="border-b bg-gray-50 dark:bg-gray-900 flex items-center justify-between dark:border-gray-700">
|
||||||
<div>
|
<div>
|
||||||
{(hasChanges || hasHookChanges) && (
|
{(hasChanges || hasHookChanges) && (
|
||||||
<p className="text-sm text-orange-600 mt-1">
|
<p className="text-sm text-orange-600 mt-1">
|
||||||
|
|
@ -488,22 +488,22 @@ const PropertyPanel: React.FC<PropertyPanelProps> = ({
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
{/* Tabs */}
|
{/* Tabs */}
|
||||||
<div className="flex gap-2 mt-4">
|
<div className="flex gap-2 p-1">
|
||||||
<button
|
<button
|
||||||
className={`px-3 py-1 rounded-t-md font-medium border-b-2 transition-colors ${
|
className={`px-3 py-1 font-medium border-b-2 transition-colors ${
|
||||||
activeTab === "props"
|
activeTab === "props"
|
||||||
? "border-blue-500 text-blue-700 bg-white"
|
? "border-blue-500 text-blue-700 bg-white dark:bg-gray-900 dark:text-blue-400 dark:border-blue-400"
|
||||||
: "border-transparent text-gray-500 bg-gray-100"
|
: "border-transparent text-gray-500 bg-gray-100 dark:bg-gray-800 dark:text-gray-400 dark:border-transparent"
|
||||||
}`}
|
}`}
|
||||||
onClick={() => setActiveTab("props")}
|
onClick={() => setActiveTab("props")}
|
||||||
>
|
>
|
||||||
Properties
|
Properties
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
className={`px-3 py-1 rounded-t-md font-medium border-b-2 transition-colors ${
|
className={`px-3 py-1 font-medium border-b-2 transition-colors ${
|
||||||
activeTab === "hooks"
|
activeTab === "hooks"
|
||||||
? "border-blue-500 text-blue-700 bg-white"
|
? "border-blue-500 text-blue-700 bg-white dark:bg-gray-900 dark:text-blue-400 dark:border-blue-400"
|
||||||
: "border-transparent text-gray-500 bg-gray-100"
|
: "border-transparent text-gray-500 bg-gray-100 dark:bg-gray-800 dark:text-gray-400 dark:border-transparent"
|
||||||
}`}
|
}`}
|
||||||
onClick={() => setActiveTab("hooks")}
|
onClick={() => setActiveTab("hooks")}
|
||||||
>
|
>
|
||||||
|
|
@ -515,7 +515,7 @@ const PropertyPanel: React.FC<PropertyPanelProps> = ({
|
||||||
<Button
|
<Button
|
||||||
variant="solid"
|
variant="solid"
|
||||||
size="sm"
|
size="sm"
|
||||||
className="mr-2 px-3 py-1 rounded bg-red-500 text-white hover:bg-red-600 transition-colors text-sm"
|
className="mr-2 px-3 py-1 rounded bg-red-500 text-white hover:bg-red-600 dark:bg-red-700 dark:hover:bg-red-800 dark:text-white transition-colors text-sm"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (selectedComponent) {
|
if (selectedComponent) {
|
||||||
if (
|
if (
|
||||||
|
|
@ -578,8 +578,8 @@ const PropertyPanel: React.FC<PropertyPanelProps> = ({
|
||||||
|
|
||||||
{/* Content */}
|
{/* Content */}
|
||||||
{activeTab === "props" && (
|
{activeTab === "props" && (
|
||||||
<div className="flex-1 text-black overflow-y-auto p-4 max-h-[calc(100vh-200px)]">
|
<div className="flex-1 text-black dark:text-gray-200 overflow-y-auto p-4 max-h-[calc(100vh-200px)] bg-white dark:bg-gray-900">
|
||||||
<h3 className="text-md font-medium text-gray-800 mb-4">Properties</h3>
|
<h3 className="text-md font-medium text-gray-800 dark:text-gray-100 mb-4">Properties</h3>
|
||||||
{/* Properties */}
|
{/* Properties */}
|
||||||
{properties.length > 0 && (
|
{properties.length > 0 && (
|
||||||
<div>{properties.map(renderPropertyControl)}</div>
|
<div>{properties.map(renderPropertyControl)}</div>
|
||||||
|
|
@ -587,7 +587,7 @@ const PropertyPanel: React.FC<PropertyPanelProps> = ({
|
||||||
{/* Events */}
|
{/* Events */}
|
||||||
{events.length > 0 && (
|
{events.length > 0 && (
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-md font-medium text-gray-800 mb-4 mt-6">
|
<h3 className="text-md font-medium text-gray-800 dark:text-gray-100 mb-4 mt-6">
|
||||||
Events
|
Events
|
||||||
</h3>
|
</h3>
|
||||||
{events.map(renderPropertyControl)}
|
{events.map(renderPropertyControl)}
|
||||||
|
|
@ -596,7 +596,7 @@ const PropertyPanel: React.FC<PropertyPanelProps> = ({
|
||||||
{/* Styling */}
|
{/* Styling */}
|
||||||
{styling.length > 0 && (
|
{styling.length > 0 && (
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-md font-medium text-gray-800 mb-4 mt-6">
|
<h3 className="text-md font-medium text-gray-800 dark:text-gray-100 mb-4 mt-6">
|
||||||
Styling
|
Styling
|
||||||
</h3>
|
</h3>
|
||||||
{styling.map(renderPropertyControl)}
|
{styling.map(renderPropertyControl)}
|
||||||
|
|
|
||||||
|
|
@ -88,8 +88,8 @@ export const Splitter: React.FC<SplitterProps> = ({
|
||||||
${
|
${
|
||||||
isHorizontal ? "w-1 cursor-col-resize" : "h-1 cursor-row-resize"
|
isHorizontal ? "w-1 cursor-col-resize" : "h-1 cursor-row-resize"
|
||||||
}
|
}
|
||||||
bg-gray-300 hover:bg-blue-500 transition-colors duration-200 flex-shrink-0
|
bg-gray-300 dark:bg-gray-700 hover:bg-blue-500 dark:hover:bg-blue-600 transition-colors duration-200 flex-shrink-0
|
||||||
${isDragging ? "bg-blue-500" : ""}
|
${isDragging ? "bg-blue-500 dark:bg-blue-600" : ""}
|
||||||
`}
|
`}
|
||||||
onMouseDown={handleMouseDown}
|
onMouseDown={handleMouseDown}
|
||||||
/>
|
/>
|
||||||
|
|
@ -124,8 +124,8 @@ export const Splitter: React.FC<SplitterProps> = ({
|
||||||
${
|
${
|
||||||
isHorizontal ? "w-1 cursor-col-resize" : "h-1 cursor-row-resize"
|
isHorizontal ? "w-1 cursor-col-resize" : "h-1 cursor-row-resize"
|
||||||
}
|
}
|
||||||
bg-gray-300 hover:bg-blue-500 transition-colors duration-200 flex-shrink-0
|
bg-gray-300 dark:bg-gray-700 hover:bg-blue-500 dark:hover:bg-blue-600 transition-colors duration-200 flex-shrink-0
|
||||||
${isDragging ? "bg-blue-500" : ""}
|
${isDragging ? "bg-blue-500 dark:bg-blue-600" : ""}
|
||||||
`}
|
`}
|
||||||
onMouseDown={handleMouseDown}
|
onMouseDown={handleMouseDown}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -51,17 +51,17 @@ export default function Widget({
|
||||||
};
|
};
|
||||||
}, [icon]);
|
}, [icon]);
|
||||||
|
|
||||||
const colorMap: Record<string, { bg: string; text: string }> = {
|
const colorMap: Record<string, { bg: string; text: string; darkBg: string; darkText: string }> = {
|
||||||
blue: { bg: "from-blue-100 to-blue-200", text: "text-blue-600" },
|
blue: { bg: 'from-blue-100 to-blue-200', text: 'text-blue-600', darkBg: 'from-blue-900 to-blue-800', darkText: 'text-blue-300' },
|
||||||
green: { bg: "from-green-100 to-green-200", text: "text-green-600" },
|
green: { bg: 'from-green-100 to-green-200', text: 'text-green-600', darkBg: 'from-green-900 to-green-800', darkText: 'text-green-300' },
|
||||||
purple: { bg: "from-purple-100 to-purple-200", text: "text-purple-600" },
|
purple: { bg: 'from-purple-100 to-purple-200', text: 'text-purple-600', darkBg: 'from-purple-900 to-purple-800', darkText: 'text-purple-300' },
|
||||||
gray: { bg: "from-gray-100 to-gray-200", text: "text-gray-600" },
|
gray: { bg: 'from-gray-100 to-gray-200', text: 'text-gray-600', darkBg: 'from-gray-800 to-gray-700', darkText: 'text-gray-300' },
|
||||||
red: { bg: "from-red-100 to-red-200", text: "text-red-600" },
|
red: { bg: 'from-red-100 to-red-200', text: 'text-red-600', darkBg: 'from-red-900 to-red-800', darkText: 'text-red-300' },
|
||||||
yellow: { bg: "from-yellow-100 to-yellow-200", text: "text-yellow-600" },
|
yellow: { bg: 'from-yellow-100 to-yellow-200', text: 'text-yellow-600', darkBg: 'from-yellow-900 to-yellow-800', darkText: 'text-yellow-300' },
|
||||||
pink: { bg: "from-pink-100 to-pink-200", text: "text-pink-600" },
|
pink: { bg: 'from-pink-100 to-pink-200', text: 'text-pink-600', darkBg: 'from-pink-900 to-pink-800', darkText: 'text-pink-300' },
|
||||||
indigo: { bg: "from-indigo-100 to-indigo-200", text: "text-indigo-600" },
|
indigo: { bg: 'from-indigo-100 to-indigo-200', text: 'text-indigo-600', darkBg: 'from-indigo-900 to-indigo-800', darkText: 'text-indigo-300' },
|
||||||
teal: { bg: "from-teal-100 to-teal-200", text: "text-teal-600" },
|
teal: { bg: 'from-teal-100 to-teal-200', text: 'text-teal-600', darkBg: 'from-teal-900 to-teal-800', darkText: 'text-teal-300' },
|
||||||
orange: { bg: "from-orange-100 to-orange-200", text: "text-orange-600" },
|
orange: { bg: 'from-orange-100 to-orange-200', text: 'text-orange-600', darkBg: 'from-orange-900 to-orange-800', darkText: 'text-orange-300' },
|
||||||
};
|
};
|
||||||
|
|
||||||
const safeColor = color && colorMap[color] ? color : "green";
|
const safeColor = color && colorMap[color] ? color : "green";
|
||||||
|
|
@ -70,28 +70,32 @@ export default function Widget({
|
||||||
<div
|
<div
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
className={classNames(
|
className={classNames(
|
||||||
"bg-white rounded-lg shadow-sm border border-gray-200 p-4 flex flex-col justify-between",
|
'bg-white dark:bg-gray-900 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 p-4 flex flex-col justify-between',
|
||||||
onClick && "cursor-pointer hover:bg-gray-50",
|
onClick && 'cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800',
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<p className="text-sm font-semibold text-gray-600 tracking-wide">
|
<p className="text-sm font-semibold text-gray-600 dark:text-gray-300 tracking-wide">
|
||||||
{title}
|
{title}
|
||||||
</p>
|
</p>
|
||||||
<p
|
<p
|
||||||
className={`${valueClassName} font-bold mt-1 ${colorMap[safeColor].text}`}
|
className={classNames(valueClassName, 'font-bold mt-1', colorMap[safeColor].text, 'dark:' + colorMap[safeColor].darkText)}
|
||||||
>
|
>
|
||||||
{value}
|
{value}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-sm text-gray-500 mt-1">{subTitle}</p>
|
<p className="text-sm text-gray-500 dark:text-gray-400 mt-1">{subTitle}</p>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className={`w-12 h-12 bg-gradient-to-br ${colorMap[safeColor].bg} rounded-xl flex items-center justify-center shadow-sm`}
|
className={classNames(
|
||||||
|
'w-12 h-12 bg-gradient-to-br rounded-xl flex items-center justify-center shadow-sm',
|
||||||
|
colorMap[safeColor].bg,
|
||||||
|
'dark:' + colorMap[safeColor].darkBg
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
{IconComponent ? (
|
{IconComponent ? (
|
||||||
<IconComponent className={`w-6 h-6 ${colorMap[safeColor].text}`} />
|
<IconComponent className={classNames('w-6 h-6', colorMap[safeColor].text, 'dark:' + colorMap[safeColor].darkText)} />
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -12,13 +12,13 @@ const ComponentPreview: React.FC<ComponentPreviewProps> = ({ componentName, clas
|
||||||
const { components, loading } = useComponents()
|
const { components, loading } = useComponents()
|
||||||
|
|
||||||
if (!componentName) {
|
if (!componentName) {
|
||||||
return <div className="text-sm text-gray-500">Bileşen ismi yok.</div>
|
return <div className="text-sm text-gray-500 dark:text-gray-400">Bileşen ismi yok.</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
// components dizisinin varlığını kontrol et
|
// components dizisinin varlığını kontrol et
|
||||||
if (loading || !components || !Array.isArray(components)) {
|
if (loading || !components || !Array.isArray(components)) {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-center min-h-screen bg-gray-50">
|
<div className="flex items-center justify-center min-h-screen bg-gray-50 dark:bg-gray-900">
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<Loading loading={true} />
|
<Loading loading={true} />
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -47,7 +47,7 @@ const ComponentPreview: React.FC<ComponentPreviewProps> = ({ componentName, clas
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`bg-white ${className}`}>
|
<div className={`bg-white dark:bg-gray-900 ${className}`}>
|
||||||
<DynamicRenderer componentName={componentName} dependencies={dependencies} />
|
<DynamicRenderer componentName={componentName} dependencies={dependencies} />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -193,7 +193,7 @@ const DynamicRenderer: React.FC<DynamicRendererProps> = ({
|
||||||
|
|
||||||
if (!Component)
|
if (!Component)
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-center min-h-screen bg-gray-50">
|
<div className="flex items-center justify-center min-h-screen bg-gray-50 dark:bg-gray-900">
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<Loading loading={!Component} />
|
<Loading loading={!Component} />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -51,17 +51,17 @@ export default function Widget({
|
||||||
}
|
}
|
||||||
}, [icon])
|
}, [icon])
|
||||||
|
|
||||||
const colorMap: Record<string, { bg: string; text: string }> = {
|
const colorMap: Record<string, { bg: string; text: string; darkBg: string; darkText: string }> = {
|
||||||
blue: { bg: 'from-blue-100 to-blue-200', text: 'text-blue-600' },
|
blue: { bg: 'from-blue-100 to-blue-200', text: 'text-blue-600', darkBg: 'from-blue-900 to-blue-800', darkText: 'text-blue-300' },
|
||||||
green: { bg: 'from-green-100 to-green-200', text: 'text-green-600' },
|
green: { bg: 'from-green-100 to-green-200', text: 'text-green-600', darkBg: 'from-green-900 to-green-800', darkText: 'text-green-300' },
|
||||||
purple: { bg: 'from-purple-100 to-purple-200', text: 'text-purple-600' },
|
purple: { bg: 'from-purple-100 to-purple-200', text: 'text-purple-600', darkBg: 'from-purple-900 to-purple-800', darkText: 'text-purple-300' },
|
||||||
gray: { bg: 'from-gray-100 to-gray-200', text: 'text-gray-600' },
|
gray: { bg: 'from-gray-100 to-gray-200', text: 'text-gray-600', darkBg: 'from-gray-800 to-gray-700', darkText: 'text-gray-300' },
|
||||||
red: { bg: 'from-red-100 to-red-200', text: 'text-red-600' },
|
red: { bg: 'from-red-100 to-red-200', text: 'text-red-600', darkBg: 'from-red-900 to-red-800', darkText: 'text-red-300' },
|
||||||
yellow: { bg: 'from-yellow-100 to-yellow-200', text: 'text-yellow-600' },
|
yellow: { bg: 'from-yellow-100 to-yellow-200', text: 'text-yellow-600', darkBg: 'from-yellow-900 to-yellow-800', darkText: 'text-yellow-300' },
|
||||||
pink: { bg: 'from-pink-100 to-pink-200', text: 'text-pink-600' },
|
pink: { bg: 'from-pink-100 to-pink-200', text: 'text-pink-600', darkBg: 'from-pink-900 to-pink-800', darkText: 'text-pink-300' },
|
||||||
indigo: { bg: 'from-indigo-100 to-indigo-200', text: 'text-indigo-600' },
|
indigo: { bg: 'from-indigo-100 to-indigo-200', text: 'text-indigo-600', darkBg: 'from-indigo-900 to-indigo-800', darkText: 'text-indigo-300' },
|
||||||
teal: { bg: 'from-teal-100 to-teal-200', text: 'text-teal-600' },
|
teal: { bg: 'from-teal-100 to-teal-200', text: 'text-teal-600', darkBg: 'from-teal-900 to-teal-800', darkText: 'text-teal-300' },
|
||||||
orange: { bg: 'from-orange-100 to-orange-200', text: 'text-orange-600' },
|
orange: { bg: 'from-orange-100 to-orange-200', text: 'text-orange-600', darkBg: 'from-orange-900 to-orange-800', darkText: 'text-orange-300' },
|
||||||
}
|
}
|
||||||
|
|
||||||
const safeColor = color && colorMap[color] ? color : 'green'
|
const safeColor = color && colorMap[color] ? color : 'green'
|
||||||
|
|
@ -70,22 +70,26 @@ export default function Widget({
|
||||||
<div
|
<div
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
className={classNames(
|
className={classNames(
|
||||||
'bg-white rounded-lg shadow-sm border border-gray-200 p-4 flex flex-col justify-between',
|
'bg-white dark:bg-gray-900 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 p-4 flex flex-col justify-between',
|
||||||
onClick && 'cursor-pointer hover:bg-gray-50',
|
onClick && 'cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800',
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<p className="text-sm font-semibold text-gray-600">{title}</p>
|
<p className="text-sm font-semibold text-gray-600 dark:text-gray-300">{title}</p>
|
||||||
<p className={`${valueClassName} font-bold mt-1 ${colorMap[safeColor].text}`}>{value}</p>
|
<p className={classNames(valueClassName, 'font-bold mt-1', colorMap[safeColor].text, 'dark:' + colorMap[safeColor].darkText)}>{value}</p>
|
||||||
<p className="text-sm text-gray-500 mt-1">{subTitle}</p>
|
<p className="text-sm text-gray-500 dark:text-gray-400 mt-1">{subTitle}</p>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className={`w-12 h-12 bg-gradient-to-br ${colorMap[safeColor].bg} rounded-xl flex items-center justify-center shadow-sm`}
|
className={classNames(
|
||||||
|
'w-12 h-12 bg-gradient-to-br rounded-xl flex items-center justify-center shadow-sm',
|
||||||
|
colorMap[safeColor].bg,
|
||||||
|
'dark:' + colorMap[safeColor].darkBg
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
{IconComponent ? (
|
{IconComponent ? (
|
||||||
<IconComponent className={`w-6 h-6 ${colorMap[safeColor].text}`} />
|
<IconComponent className={classNames('w-6 h-6', colorMap[safeColor].text, 'dark:' + colorMap[safeColor].darkText)} />
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -90,14 +90,14 @@ const ActivityLog = () => {
|
||||||
defaultTitle={APP_NAME}
|
defaultTitle={APP_NAME}
|
||||||
></Helmet>
|
></Helmet>
|
||||||
|
|
||||||
<AdaptableCard className="overflow-hidden">
|
<AdaptableCard className="overflow-hidden bg-white dark:bg-gray-800">
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
<div className="mb-5 flex items-center justify-between gap-3">
|
<div className="mb-5 flex items-center justify-between gap-3">
|
||||||
<h3 className="text-xl font-semibold md:text-2xl">
|
<h3 className="text-xl font-semibold md:text-2xl text-gray-900 dark:text-white">
|
||||||
{translate('::Abp.Identity.ActivityLogs')}
|
{translate('::Abp.Identity.ActivityLogs')}
|
||||||
</h3>
|
</h3>
|
||||||
<Button
|
<Button
|
||||||
className="lg:hidden"
|
className="lg:hidden dark:bg-gray-700 dark:text-white dark:hover:bg-gray-600"
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="twoTone"
|
variant="twoTone"
|
||||||
onClick={() => setIsFilterDrawerOpen(true)}
|
onClick={() => setIsFilterDrawerOpen(true)}
|
||||||
|
|
@ -107,7 +107,7 @@ const ActivityLog = () => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 gap-6 lg:grid-cols-[minmax(0,1fr)_340px]">
|
<div className="grid grid-cols-1 gap-6 lg:grid-cols-[minmax(0,1fr)_340px]">
|
||||||
<div className="min-w-0 rounded-xl border border-gray-200 bg-white p-4 lg:p-6">
|
<div className="min-w-0 rounded-xl border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 p-4 lg:p-6">
|
||||||
<Log
|
<Log
|
||||||
notifications={notifications}
|
notifications={notifications}
|
||||||
isLoading={loading}
|
isLoading={loading}
|
||||||
|
|
@ -117,7 +117,7 @@ const ActivityLog = () => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="hidden lg:block">
|
<div className="hidden lg:block">
|
||||||
<LogFilter filter={filter} onFilterChange={handleFilterChange} useAffix />
|
<LogFilter filter={filter} onFilterChange={handleFilterChange} useAffix className="dark:bg-gray-800" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -134,7 +134,7 @@ const ActivityLog = () => {
|
||||||
filter={filter}
|
filter={filter}
|
||||||
onFilterChange={handleFilterChange}
|
onFilterChange={handleFilterChange}
|
||||||
useAffix={false}
|
useAffix={false}
|
||||||
className="border-none p-0"
|
className="border-none p-0 dark:bg-gray-800"
|
||||||
/>
|
/>
|
||||||
</Drawer>
|
</Drawer>
|
||||||
</AdaptableCard>
|
</AdaptableCard>
|
||||||
|
|
|
||||||
|
|
@ -31,12 +31,12 @@ const Log = ({
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
{keys(notifications).map((group) => (
|
{keys(notifications).map((group) => (
|
||||||
<div key={group} className="mb-8">
|
<div key={group} className="mb-8">
|
||||||
<div className="mb-4 font-semibold uppercase">
|
<div className="mb-4 font-semibold uppercase text-gray-700 dark:text-gray-300">
|
||||||
{dayjs(group).locale(currentLocale).format('LL')}
|
{dayjs(group).locale(currentLocale).format('LL')}
|
||||||
</div>
|
</div>
|
||||||
<Timeline>
|
<Timeline className="dark:bg-gray-800">
|
||||||
{isEmpty(notifications[group]) ? (
|
{isEmpty(notifications[group]) ? (
|
||||||
<Timeline.Item>Bildirim yok</Timeline.Item>
|
<Timeline.Item className="dark:text-gray-400">Bildirim yok</Timeline.Item>
|
||||||
) : (
|
) : (
|
||||||
notifications[group].map((notification, i) => (
|
notifications[group].map((notification, i) => (
|
||||||
<Timeline.Item
|
<Timeline.Item
|
||||||
|
|
@ -46,6 +46,7 @@ const Log = ({
|
||||||
userImg={AVATAR_URL(notification.creatorId, notification.tenantId)}
|
userImg={AVATAR_URL(notification.creatorId, notification.tenantId)}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
className="dark:bg-gray-800"
|
||||||
>
|
>
|
||||||
<Event data={notification} compact={false} />
|
<Event data={notification} compact={false} />
|
||||||
</Timeline.Item>
|
</Timeline.Item>
|
||||||
|
|
@ -56,11 +57,11 @@ const Log = ({
|
||||||
))}
|
))}
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
{loadable ? (
|
{loadable ? (
|
||||||
<Button loading={isLoading} onClick={onLoadMore}>
|
<Button loading={isLoading} onClick={onLoadMore} className="dark:bg-gray-700 dark:text-white dark:hover:bg-gray-600">
|
||||||
{translate('::Abp.Identity.ActivityLogs.LoadMore')}
|
{translate('::Abp.Identity.ActivityLogs.LoadMore')}
|
||||||
</Button>
|
</Button>
|
||||||
) : (
|
) : (
|
||||||
translate('::Abp.Identity.ActivityLogs.ReceivedAllNotifications')
|
<span className="dark:text-gray-400">{translate('::Abp.Identity.ActivityLogs.ReceivedAllNotifications')}</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -44,8 +44,8 @@ const LogFilter = ({
|
||||||
const { translate } = useLocalization()
|
const { translate } = useLocalization()
|
||||||
|
|
||||||
const content = (
|
const content = (
|
||||||
<div className={classNames('rounded-xl border border-gray-200 bg-white p-4', className)}>
|
<div className={classNames('rounded-xl border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 p-4', className)}>
|
||||||
<h5 className="mb-4 text-base font-semibold">{translate('::Abp.Identity.ActivityLogs.Filters')}</h5>
|
<h5 className="mb-4 text-base font-semibold text-gray-900 dark:text-white">{translate('::Abp.Identity.ActivityLogs.Filters')}</h5>
|
||||||
<Checkbox.Group
|
<Checkbox.Group
|
||||||
vertical
|
vertical
|
||||||
value={filter}
|
value={filter}
|
||||||
|
|
@ -53,11 +53,11 @@ const LogFilter = ({
|
||||||
onFilterChange(value as string[])
|
onFilterChange(value as string[])
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<CategoryTitle className="mb-3 text-gray-500">
|
<CategoryTitle className="mb-3 text-gray-500 dark:text-gray-400">
|
||||||
{translate('::Abp.Identity.ActivityLogs.Channels')}
|
{translate('::Abp.Identity.ActivityLogs.Channels')}
|
||||||
</CategoryTitle>
|
</CategoryTitle>
|
||||||
{ticketCheckboxes.map((checkbox) => (
|
{ticketCheckboxes.map((checkbox) => (
|
||||||
<Checkbox key={checkbox.value} className="mb-3" value={checkbox.value}>
|
<Checkbox key={checkbox.value} className="mb-3 dark:text-white" value={checkbox.value}>
|
||||||
{checkbox.label}
|
{checkbox.label}
|
||||||
</Checkbox>
|
</Checkbox>
|
||||||
))}
|
))}
|
||||||
|
|
|
||||||
|
|
@ -86,16 +86,16 @@ function AuditLogs({
|
||||||
return (
|
return (
|
||||||
<Dialog width="90%" isOpen={open} onClose={onDialogClose} onRequestClose={onDialogClose}>
|
<Dialog width="90%" isOpen={open} onClose={onDialogClose} onRequestClose={onDialogClose}>
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="flex items-center justify-between mb-6 pb-4 border-b border-gray-200">
|
<div className="flex items-center justify-between mb-6 pb-4 border-b border-gray-200 dark:border-gray-700">
|
||||||
<div>
|
<div>
|
||||||
<h4 className="text-xl font-bold text-gray-800">Audit Log Details</h4>
|
<h4 className="text-xl font-bold text-gray-800 dark:text-gray-100">Audit Log Details</h4>
|
||||||
{selectedLog?.id && <p className="text-sm text-gray-500 mt-1">ID: {selectedLog.id}</p>}
|
{selectedLog?.id && <p className="text-sm text-gray-500 dark:text-gray-400 mt-1">ID: {selectedLog.id}</p>}
|
||||||
</div>
|
</div>
|
||||||
{selectedLog?.httpStatusCode && (
|
{selectedLog?.httpStatusCode && (
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
{getStatusBadge(selectedLog.httpStatusCode)}
|
{getStatusBadge(selectedLog.httpStatusCode)}
|
||||||
<Badge
|
<Badge
|
||||||
className="border border-gray-300 bg-white text-gray-700"
|
className="border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-200"
|
||||||
content={selectedLog.applicationName}
|
content={selectedLog.applicationName}
|
||||||
></Badge>
|
></Badge>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -108,32 +108,32 @@ function AuditLogs({
|
||||||
</div>
|
</div>
|
||||||
) : !selectedLog ? (
|
) : !selectedLog ? (
|
||||||
<div className="text-center py-16">
|
<div className="text-center py-16">
|
||||||
<FaExclamationCircle className="w-16 h-16 text-gray-300 mx-auto mb-4" />
|
<FaExclamationCircle className="w-16 h-16 text-gray-300 dark:text-gray-600 mx-auto mb-4" />
|
||||||
<p className="text-gray-500">No audit log found</p>
|
<p className="text-gray-500 dark:text-gray-400">No audit log found</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<Tabs defaultValue="log" variant="pill">
|
<Tabs defaultValue="log" variant="pill">
|
||||||
<TabList className="mb-6 bg-gray-50 p-1 rounded-lg">
|
<TabList className="mb-6 bg-gray-50 dark:bg-gray-800 p-1 rounded-lg">
|
||||||
<TabNav value="log">
|
<TabNav value="log">
|
||||||
<FaRegFileAlt className="w-4 h-4 mr-2" />
|
<FaRegFileAlt className="w-4 h-4 mr-2" />
|
||||||
Overview
|
<span className="text-gray-700 dark:text-gray-200">Overview</span>
|
||||||
</TabNav>
|
</TabNav>
|
||||||
<TabNav value="actions">
|
<TabNav value="actions">
|
||||||
<FaCode className="w-4 h-4 mr-2" />
|
<FaCode className="w-4 h-4 mr-2" />
|
||||||
Actions
|
<span className="text-gray-700 dark:text-gray-200">Actions</span>
|
||||||
{selectedLog.actions?.length > 0 && (
|
{selectedLog.actions?.length > 0 && (
|
||||||
<Badge
|
<Badge
|
||||||
className="ml-2 bg-blue-500"
|
className="ml-2 bg-blue-500 dark:bg-blue-700"
|
||||||
content={`${selectedLog.actions.length}`}
|
content={`${selectedLog.actions.length}`}
|
||||||
></Badge>
|
></Badge>
|
||||||
)}
|
)}
|
||||||
</TabNav>
|
</TabNav>
|
||||||
<TabNav value="changes">
|
<TabNav value="changes">
|
||||||
<FaRegCheckCircle className="w-4 h-4 mr-2" />
|
<FaRegCheckCircle className="w-4 h-4 mr-2" />
|
||||||
Entity Changes
|
<span className="text-gray-700 dark:text-gray-200">Entity Changes</span>
|
||||||
{selectedLog.entityChanges?.length > 0 && (
|
{selectedLog.entityChanges?.length > 0 && (
|
||||||
<Badge
|
<Badge
|
||||||
className="ml-2 bg-purple-500"
|
className="ml-2 bg-purple-500 dark:bg-purple-700"
|
||||||
content={`${selectedLog.entityChanges.length}`}
|
content={`${selectedLog.entityChanges.length}`}
|
||||||
></Badge>
|
></Badge>
|
||||||
)}
|
)}
|
||||||
|
|
@ -144,9 +144,9 @@ function AuditLogs({
|
||||||
<TabContent value="log">
|
<TabContent value="log">
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||||
{/* Request Information */}
|
{/* Request Information */}
|
||||||
<AdaptableCard className="shadow-sm">
|
<AdaptableCard className="shadow-sm dark:bg-gray-900 dark:border-gray-700">
|
||||||
<h6 className="font-semibold text-gray-700 mb-4 flex items-center gap-2">
|
<h6 className="font-semibold text-gray-700 dark:text-gray-200 mb-4 flex items-center gap-2">
|
||||||
<FaGlobe className="w-5 h-5 text-blue-500" />
|
<FaGlobe className="w-5 h-5 text-blue-500 dark:text-blue-400" />
|
||||||
Request Information
|
Request Information
|
||||||
</h6>
|
</h6>
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
|
|
@ -154,7 +154,7 @@ function AuditLogs({
|
||||||
icon={FaUser}
|
icon={FaUser}
|
||||||
label="User"
|
label="User"
|
||||||
value={selectedLog.userName || 'Anonymous'}
|
value={selectedLog.userName || 'Anonymous'}
|
||||||
valueClassName="font-medium text-gray-800"
|
valueClassName="font-medium text-gray-800 dark:text-gray-100"
|
||||||
/>
|
/>
|
||||||
<InfoRow
|
<InfoRow
|
||||||
icon={FaRegClock}
|
icon={FaRegClock}
|
||||||
|
|
@ -187,9 +187,9 @@ function AuditLogs({
|
||||||
</AdaptableCard>
|
</AdaptableCard>
|
||||||
|
|
||||||
{/* HTTP Details */}
|
{/* HTTP Details */}
|
||||||
<AdaptableCard className="shadow-sm">
|
<AdaptableCard className="shadow-sm dark:bg-gray-900 dark:border-gray-700">
|
||||||
<h6 className="font-semibold text-gray-700 mb-4 flex items-center gap-2">
|
<h6 className="font-semibold text-gray-700 dark:text-gray-200 mb-4 flex items-center gap-2">
|
||||||
<FaCode className="w-5 h-5 text-green-500" />
|
<FaCode className="w-5 h-5 text-green-500 dark:text-green-400" />
|
||||||
HTTP Details
|
HTTP Details
|
||||||
</h6>
|
</h6>
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
|
|
@ -222,13 +222,13 @@ function AuditLogs({
|
||||||
icon={FaGlobe}
|
icon={FaGlobe}
|
||||||
label="URL"
|
label="URL"
|
||||||
value={selectedLog.url}
|
value={selectedLog.url}
|
||||||
valueClassName="text-blue-600 font-mono text-xs"
|
valueClassName="text-blue-600 dark:text-blue-400 font-mono text-xs"
|
||||||
/>
|
/>
|
||||||
<InfoRow
|
<InfoRow
|
||||||
icon={FaRegFileAlt}
|
icon={FaRegFileAlt}
|
||||||
label="Browser"
|
label="Browser"
|
||||||
value={
|
value={
|
||||||
<span className="text-xs text-gray-600 line-clamp-2">
|
<span className="text-xs text-gray-600 dark:text-gray-400 line-clamp-2">
|
||||||
{selectedLog.browserInfo || 'Unknown'}
|
{selectedLog.browserInfo || 'Unknown'}
|
||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
|
|
@ -238,12 +238,12 @@ function AuditLogs({
|
||||||
|
|
||||||
{/* Exceptions (Full Width) */}
|
{/* Exceptions (Full Width) */}
|
||||||
{selectedLog.exceptions && (
|
{selectedLog.exceptions && (
|
||||||
<AdaptableCard className="lg:col-span-2 shadow-sm border-l-4 border-red-500">
|
<AdaptableCard className="lg:col-span-2 shadow-sm border-l-4 border-red-500 dark:bg-gray-900 dark:border-gray-700">
|
||||||
<h6 className="font-semibold text-red-600 mb-3 flex items-center gap-2">
|
<h6 className="font-semibold text-red-600 dark:text-red-400 mb-3 flex items-center gap-2">
|
||||||
<FaExclamationCircle className="w-5 h-5" />
|
<FaExclamationCircle className="w-5 h-5" />
|
||||||
Exceptions
|
Exceptions
|
||||||
</h6>
|
</h6>
|
||||||
<pre className="bg-red-50 text-red-700 p-4 rounded-lg text-xs whitespace-pre-wrap leading-relaxed font-mono overflow-x-auto">
|
<pre className="bg-red-50 dark:bg-red-950 text-red-700 dark:text-red-300 p-4 rounded-lg text-xs whitespace-pre-wrap leading-relaxed font-mono overflow-x-auto">
|
||||||
{selectedLog.exceptions}
|
{selectedLog.exceptions}
|
||||||
</pre>
|
</pre>
|
||||||
</AdaptableCard>
|
</AdaptableCard>
|
||||||
|
|
@ -251,12 +251,12 @@ function AuditLogs({
|
||||||
|
|
||||||
{/* Comments */}
|
{/* Comments */}
|
||||||
{selectedLog.comments && (
|
{selectedLog.comments && (
|
||||||
<AdaptableCard className="lg:col-span-2 shadow-sm bg-blue-50">
|
<AdaptableCard className="lg:col-span-2 shadow-sm bg-blue-50 dark:bg-blue-950 dark:border-gray-700">
|
||||||
<h6 className="font-semibold text-blue-700 mb-2 flex items-center gap-2">
|
<h6 className="font-semibold text-blue-700 dark:text-blue-300 mb-2 flex items-center gap-2">
|
||||||
<FaRegFileAlt className="w-5 h-5" />
|
<FaRegFileAlt className="w-5 h-5" />
|
||||||
Comments
|
Comments
|
||||||
</h6>
|
</h6>
|
||||||
<p className="text-sm text-blue-800">{selectedLog.comments}</p>
|
<p className="text-sm text-blue-800 dark:text-blue-200">{selectedLog.comments}</p>
|
||||||
</AdaptableCard>
|
</AdaptableCard>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -266,37 +266,37 @@ function AuditLogs({
|
||||||
<TabContent value="actions">
|
<TabContent value="actions">
|
||||||
{!selectedLog.actions || selectedLog.actions.length === 0 ? (
|
{!selectedLog.actions || selectedLog.actions.length === 0 ? (
|
||||||
<div className="text-center py-16">
|
<div className="text-center py-16">
|
||||||
<FaCode className="w-16 h-16 text-gray-300 mx-auto mb-4" />
|
<FaCode className="w-16 h-16 text-gray-300 dark:text-gray-600 mx-auto mb-4" />
|
||||||
<p className="text-gray-500">No actions recorded</p>
|
<p className="text-gray-500 dark:text-gray-400">No actions recorded</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
{selectedLog.actions.map((action, index) => (
|
{selectedLog.actions.map((action, index) => (
|
||||||
<AdaptableCard
|
<AdaptableCard
|
||||||
key={index}
|
key={index}
|
||||||
className="shadow-sm hover:shadow-md transition-shadow"
|
className="shadow-sm hover:shadow-md transition-shadow dark:bg-gray-900 dark:border-gray-700"
|
||||||
>
|
>
|
||||||
<div className="flex items-start justify-between mb-4">
|
<div className="flex items-start justify-between mb-4">
|
||||||
<h6 className="font-semibold text-gray-700 flex items-center gap-2">
|
<h6 className="font-semibold text-gray-700 dark:text-gray-200 flex items-center gap-2">
|
||||||
<Badge className="bg-indigo-500" content={`#${index + 1}`}></Badge>
|
<Badge className="bg-indigo-500 dark:bg-indigo-700" content={`#${index + 1}`}></Badge>
|
||||||
<span className="text-sm">{action.methodName}</span>
|
<span className="text-sm">{action.methodName}</span>
|
||||||
</h6>
|
</h6>
|
||||||
<Badge
|
<Badge
|
||||||
className="border border-gray-300 bg-white text-gray-700 text-xs"
|
className="border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-200 text-xs"
|
||||||
content={formatDuration(action.executionDuration)}
|
content={formatDuration(action.executionDuration)}
|
||||||
></Badge>
|
></Badge>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
|
||||||
<div>
|
<div>
|
||||||
<p className="text-xs text-gray-500 mb-1">Service Name</p>
|
<p className="text-xs text-gray-500 dark:text-gray-400 mb-1">Service Name</p>
|
||||||
<p className="text-sm font-mono text-gray-700 break-all">
|
<p className="text-sm font-mono text-gray-700 dark:text-gray-200 break-all">
|
||||||
{action.serviceName}
|
{action.serviceName}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-xs text-gray-500 mb-1">Execution Time</p>
|
<p className="text-xs text-gray-500 dark:text-gray-400 mb-1">Execution Time</p>
|
||||||
<p className="text-sm text-gray-700">
|
<p className="text-sm text-gray-700 dark:text-gray-200">
|
||||||
{new Date(action.executionTime).toLocaleString('tr-TR')}
|
{new Date(action.executionTime).toLocaleString('tr-TR')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -304,8 +304,8 @@ function AuditLogs({
|
||||||
|
|
||||||
{action.parameters && (
|
{action.parameters && (
|
||||||
<div>
|
<div>
|
||||||
<p className="text-xs text-gray-500 mb-2">Parameters</p>
|
<p className="text-xs text-gray-500 dark:text-gray-400 mb-2">Parameters</p>
|
||||||
<pre className="bg-gray-50 border border-gray-200 p-3 rounded-lg text-xs overflow-x-auto">
|
<pre className="bg-gray-50 dark:bg-gray-800 border border-gray-200 dark:border-gray-700 p-3 rounded-lg text-xs overflow-x-auto text-gray-700 dark:text-gray-200">
|
||||||
{JSON.stringify(JSON.parse(action.parameters), null, 2)}
|
{JSON.stringify(JSON.parse(action.parameters), null, 2)}
|
||||||
</pre>
|
</pre>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -320,29 +320,29 @@ function AuditLogs({
|
||||||
<TabContent value="changes">
|
<TabContent value="changes">
|
||||||
{!selectedLog.entityChanges || selectedLog.entityChanges.length === 0 ? (
|
{!selectedLog.entityChanges || selectedLog.entityChanges.length === 0 ? (
|
||||||
<div className="text-center py-16">
|
<div className="text-center py-16">
|
||||||
<FaRegCheckCircle className="w-16 h-16 text-gray-300 mx-auto mb-4" />
|
<FaRegCheckCircle className="w-16 h-16 text-gray-300 dark:text-gray-600 mx-auto mb-4" />
|
||||||
<p className="text-gray-500">No entity changes recorded</p>
|
<p className="text-gray-500 dark:text-gray-400">No entity changes recorded</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
{selectedLog.entityChanges.map((change, index) => (
|
{selectedLog.entityChanges.map((change, index) => (
|
||||||
<AdaptableCard
|
<AdaptableCard
|
||||||
key={index}
|
key={index}
|
||||||
className="shadow-sm hover:shadow-md transition-shadow"
|
className="shadow-sm hover:shadow-md transition-shadow dark:bg-gray-900 dark:border-gray-700"
|
||||||
>
|
>
|
||||||
<div className="flex items-start justify-between mb-4">
|
<div className="flex items-start justify-between mb-4">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<Badge className="bg-purple-500" content={`#${index + 1}`}></Badge>
|
<Badge className="bg-purple-500 dark:bg-purple-700" content={`#${index + 1}`}></Badge>
|
||||||
<div>
|
<div>
|
||||||
<h6 className="font-semibold text-gray-700">
|
<h6 className="font-semibold text-gray-700 dark:text-gray-200">
|
||||||
{change.entityTypeFullName}
|
{change.entityTypeFullName}
|
||||||
</h6>
|
</h6>
|
||||||
<p className="text-xs text-gray-500">ID: {change.entityId}</p>
|
<p className="text-xs text-gray-500 dark:text-gray-400">ID: {change.entityId}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
{getChangeTypeBadge(change.changeType)}
|
{getChangeTypeBadge(change.changeType)}
|
||||||
<span className="text-xs text-gray-500">
|
<span className="text-xs text-gray-500 dark:text-gray-400">
|
||||||
{new Date(change.changeTime).toLocaleTimeString('tr-TR')}
|
{new Date(change.changeTime).toLocaleTimeString('tr-TR')}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -350,16 +350,16 @@ function AuditLogs({
|
||||||
|
|
||||||
{change.propertyChanges && change.propertyChanges.length > 0 && (
|
{change.propertyChanges && change.propertyChanges.length > 0 && (
|
||||||
<div>
|
<div>
|
||||||
<p className="text-xs font-medium text-gray-600 mb-3">Property Changes</p>
|
<p className="text-xs font-medium text-gray-600 dark:text-gray-300 mb-3">Property Changes</p>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
{change.propertyChanges.map((prop, i) => (
|
{change.propertyChanges.map((prop, i) => (
|
||||||
<div key={i} className="bg-gray-50 p-3 rounded-lg">
|
<div key={i} className="bg-gray-50 dark:bg-gray-800 p-3 rounded-lg">
|
||||||
<div className="flex items-start justify-between gap-4">
|
<div className="flex items-start justify-between gap-4">
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<code className="text-sm font-semibold text-indigo-600">
|
<code className="text-sm font-semibold text-indigo-600 dark:text-indigo-400">
|
||||||
{prop.propertyName}
|
{prop.propertyName}
|
||||||
</code>
|
</code>
|
||||||
<span className="text-xs text-gray-500 ml-2">
|
<span className="text-xs text-gray-500 dark:text-gray-400 ml-2">
|
||||||
({prop.propertyTypeFullName?.split('.').pop()})
|
({prop.propertyTypeFullName?.split('.').pop()})
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -367,16 +367,16 @@ function AuditLogs({
|
||||||
{!prop.originalValue ||
|
{!prop.originalValue ||
|
||||||
prop.originalValue === 'null' ||
|
prop.originalValue === 'null' ||
|
||||||
prop.originalValue === '[Not Tracked]' ? (
|
prop.originalValue === '[Not Tracked]' ? (
|
||||||
<span className="px-3 py-1 bg-green-100 text-green-700 rounded-md font-medium">
|
<span className="px-3 py-1 bg-green-100 dark:bg-green-900 text-green-700 dark:text-green-300 rounded-md font-medium">
|
||||||
{prop.newValue || 'null'}
|
{prop.newValue || 'null'}
|
||||||
</span>
|
</span>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<span className="px-3 py-1 bg-gray-100 text-gray-600 rounded-md">
|
<span className="px-3 py-1 bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-300 rounded-md">
|
||||||
{prop.originalValue}
|
{prop.originalValue}
|
||||||
</span>
|
</span>
|
||||||
<span className="text-gray-400">→</span>
|
<span className="text-gray-400 dark:text-gray-500">→</span>
|
||||||
<span className="px-3 py-1 bg-yellow-100 text-yellow-700 rounded-md font-medium">
|
<span className="px-3 py-1 bg-yellow-100 dark:bg-yellow-900 text-yellow-700 dark:text-yellow-300 rounded-md font-medium">
|
||||||
{prop.newValue || 'null'}
|
{prop.newValue || 'null'}
|
||||||
</span>
|
</span>
|
||||||
</>
|
</>
|
||||||
|
|
|
||||||
|
|
@ -804,22 +804,22 @@ const OrgChart = () => {
|
||||||
|
|
||||||
<div className="flex flex-col h-full">
|
<div className="flex flex-col h-full">
|
||||||
{/* Toolbar */}
|
{/* Toolbar */}
|
||||||
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-2 pb-1 border-b">
|
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-2 pb-1 border-b border-slate-200 dark:border-gray-700">
|
||||||
<div className="flex items-center gap-3 min-w-0">
|
<div className="flex items-center gap-3 min-w-0">
|
||||||
{MenuIcon}
|
{MenuIcon}
|
||||||
<h4 className="text-sm font-medium truncate">
|
<h4 className="text-sm font-medium truncate text-gray-900 dark:text-white">
|
||||||
{translate('::App.Definitions.OrgChart') || 'Organizasyon Şeması'}
|
{translate('::App.Definitions.OrgChart') || 'Organizasyon Şeması'}
|
||||||
</h4>
|
</h4>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-wrap items-center gap-2">
|
<div className="flex flex-wrap items-center gap-2">
|
||||||
<div className="flex items-center bg-slate-100 rounded-lg p-1 gap-1">
|
<div className="flex items-center bg-slate-100 dark:bg-gray-800 rounded-lg p-1 gap-1">
|
||||||
<button
|
<button
|
||||||
onClick={() => setMode('department')}
|
onClick={() => setMode('department')}
|
||||||
className={`flex items-center gap-1.5 px-2 py-1.5 rounded-md text-xs sm:text-sm font-medium transition-colors ${
|
className={`flex items-center gap-1.5 px-2 py-1.5 rounded-md text-xs sm:text-sm font-medium transition-colors ${
|
||||||
mode === 'department'
|
mode === 'department'
|
||||||
? 'bg-blue-600 text-white shadow-sm'
|
? 'bg-blue-600 text-white shadow-sm'
|
||||||
: 'text-slate-600 hover:text-slate-800'
|
: 'text-slate-600 dark:text-gray-300 hover:text-slate-800 dark:hover:text-white'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<FaBuilding className="w-3.5 h-3.5 flex-shrink-0" />
|
<FaBuilding className="w-3.5 h-3.5 flex-shrink-0" />
|
||||||
|
|
@ -830,7 +830,7 @@ const OrgChart = () => {
|
||||||
className={`flex items-center gap-1.5 px-2 py-1.5 rounded-md text-xs sm:text-sm font-medium transition-colors ${
|
className={`flex items-center gap-1.5 px-2 py-1.5 rounded-md text-xs sm:text-sm font-medium transition-colors ${
|
||||||
mode === 'jobPosition'
|
mode === 'jobPosition'
|
||||||
? 'bg-purple-600 text-white shadow-sm'
|
? 'bg-purple-600 text-white shadow-sm'
|
||||||
: 'text-slate-600 hover:text-slate-800'
|
: 'text-slate-600 dark:text-gray-300 hover:text-slate-800 dark:hover:text-white'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<FaBriefcase className="w-3.5 h-3.5 flex-shrink-0" />
|
<FaBriefcase className="w-3.5 h-3.5 flex-shrink-0" />
|
||||||
|
|
@ -838,39 +838,39 @@ const OrgChart = () => {
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center bg-slate-100 rounded-lg p-1 gap-1">
|
<div className="flex items-center bg-slate-100 dark:bg-gray-800 rounded-lg p-1 gap-1">
|
||||||
<label className="flex items-center gap-2 px-2 py-1.5 rounded-md text-xs sm:text-sm text-slate-600 select-none cursor-pointer">
|
<label className="flex items-center gap-2 px-2 py-1.5 rounded-md text-xs sm:text-sm text-slate-600 dark:text-gray-300 select-none cursor-pointer">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={showUsers}
|
checked={showUsers}
|
||||||
onChange={(e) => setShowUsers(e.target.checked)}
|
onChange={(e) => setShowUsers(e.target.checked)}
|
||||||
className="h-4 w-4 rounded border-slate-300 text-blue-600 focus:ring-blue-500"
|
className="h-4 w-4 rounded border-slate-300 dark:border-gray-600 text-blue-600 focus:ring-blue-500"
|
||||||
/>
|
/>
|
||||||
<span className="hidden sm:inline">{translate('::App.Definitions.OrgChart.ShowUsers') || 'Kullanıcılar'}</span>
|
<span className="hidden sm:inline">{translate('::App.Definitions.OrgChart.ShowUsers') || 'Kullanıcılar'}</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center bg-slate-100 rounded-lg p-1 gap-1">
|
<div className="flex items-center bg-slate-100 dark:bg-gray-800 rounded-lg p-1 gap-1">
|
||||||
<button
|
<button
|
||||||
onClick={handleZoomOut}
|
onClick={handleZoomOut}
|
||||||
className="p-1.5 sm:p-2 rounded-md text-slate-600 hover:text-slate-800 hover:bg-white"
|
className="p-1.5 sm:p-2 rounded-md text-slate-600 dark:text-gray-300 hover:text-slate-800 dark:hover:text-white hover:bg-white dark:hover:bg-gray-700"
|
||||||
title="Zoom Out"
|
title="Zoom Out"
|
||||||
>
|
>
|
||||||
<FaSearchMinus className="w-3.5 h-3.5" />
|
<FaSearchMinus className="w-3.5 h-3.5" />
|
||||||
</button>
|
</button>
|
||||||
<span className="text-xs font-medium text-slate-600 px-1 min-w-[36px] sm:min-w-[46px] text-center">
|
<span className="text-xs font-medium text-slate-600 dark:text-gray-300 px-1 min-w-[36px] sm:min-w-[46px] text-center">
|
||||||
{Math.round(zoom * 100)}%
|
{Math.round(zoom * 100)}%
|
||||||
</span>
|
</span>
|
||||||
<button
|
<button
|
||||||
onClick={handleZoomIn}
|
onClick={handleZoomIn}
|
||||||
className="p-1.5 sm:p-2 rounded-md text-slate-600 hover:text-slate-800 hover:bg-white"
|
className="p-1.5 sm:p-2 rounded-md text-slate-600 dark:text-gray-300 hover:text-slate-800 dark:hover:text-white hover:bg-white dark:hover:bg-gray-700"
|
||||||
title="Zoom In"
|
title="Zoom In"
|
||||||
>
|
>
|
||||||
<FaSearchPlus className="w-3.5 h-3.5" />
|
<FaSearchPlus className="w-3.5 h-3.5" />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={handleZoomReset}
|
onClick={handleZoomReset}
|
||||||
className="p-1.5 sm:p-2 rounded-md text-slate-600 hover:text-slate-800 hover:bg-white"
|
className="p-1.5 sm:p-2 rounded-md text-slate-600 dark:text-gray-300 hover:text-slate-800 dark:hover:text-white hover:bg-white dark:hover:bg-gray-700"
|
||||||
title="Reset Zoom"
|
title="Reset Zoom"
|
||||||
>
|
>
|
||||||
<FaUndo className="w-3.5 h-3.5" />
|
<FaUndo className="w-3.5 h-3.5" />
|
||||||
|
|
@ -880,7 +880,7 @@ const OrgChart = () => {
|
||||||
<button
|
<button
|
||||||
onClick={handleExportJpg}
|
onClick={handleExportJpg}
|
||||||
disabled={exporting || loading}
|
disabled={exporting || loading}
|
||||||
className="flex items-center gap-1.5 px-2 py-1.5 rounded-lg text-xs sm:text-sm font-medium bg-slate-100 text-slate-600 hover:bg-slate-200 disabled:opacity-50 transition-colors"
|
className="flex items-center gap-1.5 p-1.5 sm:p-2 rounded-lg text-xs sm:text-sm font-medium bg-slate-100 dark:bg-gray-800 text-slate-600 dark:text-gray-300 hover:bg-slate-200 dark:hover:bg-gray-700 disabled:opacity-50 transition-colors"
|
||||||
title="JPG olarak indir"
|
title="JPG olarak indir"
|
||||||
>
|
>
|
||||||
<FaFileImage className="w-3.5 h-3.5 flex-shrink-0" />
|
<FaFileImage className="w-3.5 h-3.5 flex-shrink-0" />
|
||||||
|
|
|
||||||
|
|
@ -193,8 +193,8 @@ const FormEdit = () => {
|
||||||
</Badge>
|
</Badge>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<Tabs defaultValue="details" variant="pill">
|
<Tabs defaultValue="details" variant="underline">
|
||||||
<TabList className="flex-wrap border-b mb-2 bg-slate-50">
|
<TabList className="flex-wrap border-b mb-2 bg-slate-50 dark:bg-slate-800">
|
||||||
{visibleTabs.includes('details') && (
|
{visibleTabs.includes('details') && (
|
||||||
<TabNav value="details">{translate('::ListForms.ListFormEdit.TabDetails')}</TabNav>
|
<TabNav value="details">{translate('::ListForms.ListFormEdit.TabDetails')}</TabNav>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -87,7 +87,6 @@ const step1ValidationSchema = Yup.object().shape({
|
||||||
wizardName: Yup.string().required(),
|
wizardName: Yup.string().required(),
|
||||||
menuCode: Yup.string().required(),
|
menuCode: Yup.string().required(),
|
||||||
permissionGroupName: Yup.string().required(),
|
permissionGroupName: Yup.string().required(),
|
||||||
menuParentCode: Yup.string().required(),
|
|
||||||
languageTextMenuEn: Yup.string().required(),
|
languageTextMenuEn: Yup.string().required(),
|
||||||
languageTextMenuTr: Yup.string().required(),
|
languageTextMenuTr: Yup.string().required(),
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -452,7 +452,6 @@ const WizardStep1 = ({
|
||||||
|
|
||||||
const step1Missing = [
|
const step1Missing = [
|
||||||
!wizardName && translate('::ListForms.Wizard.Step1.WizardName'),
|
!wizardName && translate('::ListForms.Wizard.Step1.WizardName'),
|
||||||
!values.menuParentCode && translate('::ListForms.Wizard.Step1.MenuParent'),
|
|
||||||
!values.permissionGroupName && translate('::ListForms.Wizard.Step1.PermissionGroupName'),
|
!values.permissionGroupName && translate('::ListForms.Wizard.Step1.PermissionGroupName'),
|
||||||
!values.languageTextMenuEn && translate('::ListForms.Wizard.Step4.MenuEn'),
|
!values.languageTextMenuEn && translate('::ListForms.Wizard.Step4.MenuEn'),
|
||||||
!values.languageTextMenuTr && translate('::ListForms.Wizard.Step4.MenuTr'),
|
!values.languageTextMenuTr && translate('::ListForms.Wizard.Step4.MenuTr'),
|
||||||
|
|
@ -489,9 +488,8 @@ const WizardStep1 = ({
|
||||||
{/* Menu Parent */}
|
{/* Menu Parent */}
|
||||||
<FormItem
|
<FormItem
|
||||||
label={translate('::ListForms.Wizard.Step1.MenuParent')}
|
label={translate('::ListForms.Wizard.Step1.MenuParent')}
|
||||||
invalid={errors.menuParentCode && touched.menuParentCode}
|
invalid={false}
|
||||||
errorMessage={errors.menuParentCode}
|
errorMessage={undefined}
|
||||||
asterisk={true}
|
|
||||||
extra={
|
extra={
|
||||||
<div className="flex items-center gap-2 ml-3">
|
<div className="flex items-center gap-2 ml-3">
|
||||||
<button
|
<button
|
||||||
|
|
@ -532,7 +530,7 @@ const WizardStep1 = ({
|
||||||
nodes={menuTree}
|
nodes={menuTree}
|
||||||
rawItems={rawMenuItems}
|
rawItems={rawMenuItems}
|
||||||
isLoading={isLoadingMenu}
|
isLoading={isLoadingMenu}
|
||||||
invalid={!!(errors.menuParentCode && touched.menuParentCode)}
|
invalid={false}
|
||||||
onReload={onReloadMenu}
|
onReload={onReloadMenu}
|
||||||
initialExpanded={values.menuParentCode ? getAncestorCodes(rawMenuItems, values.menuParentCode) : undefined}
|
initialExpanded={values.menuParentCode ? getAncestorCodes(rawMenuItems, values.menuParentCode) : undefined}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -632,7 +632,7 @@ function GroupCard({
|
||||||
className={`rounded-xl border-2 transition-all ${
|
className={`rounded-xl border-2 transition-all ${
|
||||||
isOver
|
isOver
|
||||||
? 'border-indigo-400 bg-indigo-50/60 dark:bg-indigo-900/20'
|
? 'border-indigo-400 bg-indigo-50/60 dark:bg-indigo-900/20'
|
||||||
: 'border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-850'
|
: 'border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-900'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{/* Group Header */}
|
{/* Group Header */}
|
||||||
|
|
@ -642,7 +642,7 @@ function GroupCard({
|
||||||
value={group.caption}
|
value={group.caption}
|
||||||
onChange={(e) => onCaptionChange(e.target.value)}
|
onChange={(e) => onCaptionChange(e.target.value)}
|
||||||
placeholder="Group caption…"
|
placeholder="Group caption…"
|
||||||
className="flex-1 min-w-[120px] text-sm font-semibold h-7 px-2 rounded border border-gray-200 dark:border-gray-600 bg-white dark:bg-gray-800 text-gray-800 dark:text-gray-100 focus:outline-none focus:border-indigo-400"
|
className="flex-1 min-w-[120px] text-sm font-semibold h-7 px-2 rounded border border-gray-200 dark:border-gray-600 bg-gray-50 dark:bg-gray-800 text-gray-800 dark:text-gray-100 focus:outline-none focus:border-indigo-400"
|
||||||
/>
|
/>
|
||||||
{/* ColCount */}
|
{/* ColCount */}
|
||||||
<div className="flex items-center gap-1 shrink-0">
|
<div className="flex items-center gap-1 shrink-0">
|
||||||
|
|
@ -694,7 +694,7 @@ function GroupCard({
|
||||||
isOver
|
isOver
|
||||||
? 'bg-indigo-100/60 dark:bg-indigo-900/30 border border-dashed border-indigo-400'
|
? 'bg-indigo-100/60 dark:bg-indigo-900/30 border border-dashed border-indigo-400'
|
||||||
: group.items.length === 0
|
: group.items.length === 0
|
||||||
? 'border border-dashed border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800'
|
? 'border border-dashed border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800'
|
||||||
: ''
|
: ''
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -125,14 +125,14 @@ function PermissionDialogContent({
|
||||||
const permKey = permission.name || ''
|
const permKey = permission.name || ''
|
||||||
return (
|
return (
|
||||||
<div key={permission.name} className={`ml-${permission.level * 4} group`}>
|
<div key={permission.name} className={`ml-${permission.level * 4} group`}>
|
||||||
<div className="flex items-center gap-2 px-2 py-0.5 rounded-md hover:bg-gray-50 transition-all">
|
<div className="flex items-center gap-2 px-2 py-0.5 rounded-md hover:bg-gray-50 transition-all dark:hover:bg-gray-700">
|
||||||
{isParentPerm ? (
|
{isParentPerm ? (
|
||||||
<button
|
<button
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
togglePermission(permKey)
|
togglePermission(permKey)
|
||||||
}}
|
}}
|
||||||
className="w-5 h-5 flex items-center justify-center rounded hover:bg-gray-200 transition"
|
className="w-5 h-5 flex items-center justify-center rounded hover:bg-gray-200 dark:hover:bg-gray-600 transition"
|
||||||
>
|
>
|
||||||
{openPermissions[permKey] || searchTerm ? (
|
{openPermissions[permKey] || searchTerm ? (
|
||||||
<FaChevronDown className="text-gray-500 text-xs" />
|
<FaChevronDown className="text-gray-500 text-xs" />
|
||||||
|
|
@ -148,7 +148,7 @@ function PermissionDialogContent({
|
||||||
checked={permission.isGranted}
|
checked={permission.isGranted}
|
||||||
onChange={() => onClickCheckbox(permission)}
|
onChange={() => onClickCheckbox(permission)}
|
||||||
>
|
>
|
||||||
<span className="text-sm text-gray-700 group-hover:text-gray-900 transition">
|
<span className="text-sm text-gray-700 group-hover:text-gray-900 transition dark:text-gray-300 dark:group-hover:text-gray-100">
|
||||||
{translate('::' + permission.displayName)}
|
{translate('::' + permission.displayName)}
|
||||||
</span>
|
</span>
|
||||||
</Checkbox>
|
</Checkbox>
|
||||||
|
|
|
||||||
|
|
@ -119,14 +119,14 @@ function UserPermissionDialogContent({
|
||||||
const permKey = permission.name || ''
|
const permKey = permission.name || ''
|
||||||
return (
|
return (
|
||||||
<div key={permission.name} className={`ml-${permission.level * 4} group`}>
|
<div key={permission.name} className={`ml-${permission.level * 4} group`}>
|
||||||
<div className="flex items-center gap-2 px-2 py-0.5 rounded-md hover:bg-gray-50 transition-all">
|
<div className="flex items-center gap-2 px-2 py-0.5 rounded-md hover:bg-gray-50 transition-all dark:hover:bg-gray-700">
|
||||||
{isParentPerm ? (
|
{isParentPerm ? (
|
||||||
<button
|
<button
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
togglePermission(permKey)
|
togglePermission(permKey)
|
||||||
}}
|
}}
|
||||||
className="w-5 h-5 flex items-center justify-center rounded hover:bg-gray-200 transition"
|
className="w-5 h-5 flex items-center justify-center rounded hover:bg-gray-200 dark:hover:bg-gray-600 transition"
|
||||||
>
|
>
|
||||||
{openPermissions[permKey] || searchTerm ? (
|
{openPermissions[permKey] || searchTerm ? (
|
||||||
<FaChevronDown className="text-gray-500 text-xs" />
|
<FaChevronDown className="text-gray-500 text-xs" />
|
||||||
|
|
@ -145,7 +145,7 @@ function UserPermissionDialogContent({
|
||||||
(provider: any) => provider.providerName === 'R',
|
(provider: any) => provider.providerName === 'R',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<span className="text-sm text-gray-700 group-hover:text-gray-900 transition">
|
<span className="text-sm text-gray-700 group-hover:text-gray-900 transition dark:text-gray-300 dark:group-hover:text-gray-100">
|
||||||
{translate('::' + permission.displayName)}
|
{translate('::' + permission.displayName)}
|
||||||
{permission.grantedProviders.map((provider: any) => {
|
{permission.grantedProviders.map((provider: any) => {
|
||||||
const badgeContent =
|
const badgeContent =
|
||||||
|
|
|
||||||
|
|
@ -30,13 +30,13 @@ const Dashboard: React.FC = () => {
|
||||||
title={translate('::' + 'App.Videoroom.Dashboard')}
|
title={translate('::' + 'App.Videoroom.Dashboard')}
|
||||||
defaultTitle="Erp Platform"
|
defaultTitle="Erp Platform"
|
||||||
></Helmet>
|
></Helmet>
|
||||||
<div className="flex items-center justify-center p-4">
|
<div className="flex items-center justify-center p-4 bg-white dark:bg-gray-900 min-h-screen">
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{ opacity: 0, y: 20 }}
|
initial={{ opacity: 0, y: 20 }}
|
||||||
animate={{ opacity: 1, y: 0 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
className="text-center w-full max-w-4xl"
|
className="text-center w-full max-w-4xl"
|
||||||
>
|
>
|
||||||
<p className="text-lg sm:text-xl text-gray-600 mb-8 sm:mb-12">
|
<p className="text-lg sm:text-xl text-gray-600 dark:text-gray-300 mb-8 sm:mb-12">
|
||||||
{translate('::' + 'App.Videoroom.RoleSelector')}
|
{translate('::' + 'App.Videoroom.RoleSelector')}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|
@ -45,13 +45,13 @@ const Dashboard: React.FC = () => {
|
||||||
whileHover={{ scale: 1.05 }}
|
whileHover={{ scale: 1.05 }}
|
||||||
whileTap={{ scale: 0.95 }}
|
whileTap={{ scale: 0.95 }}
|
||||||
onClick={() => handleRoleSelect('teacher')}
|
onClick={() => handleRoleSelect('teacher')}
|
||||||
className="bg-white rounded-lg shadow-lg p-6 sm:p-8 hover:shadow-xl transition-all duration-300 border-2 border-transparent hover:border-blue-500"
|
className="bg-white dark:bg-gray-800 rounded-lg shadow-lg p-6 sm:p-8 hover:shadow-xl transition-all duration-300 border-2 border-transparent hover:border-blue-500 dark:hover:border-blue-400"
|
||||||
>
|
>
|
||||||
<FaCrown size={48} className="mx-auto text-blue-600 mb-4 sm:mb-4" />
|
<FaCrown size={48} className="mx-auto text-blue-600 mb-4 sm:mb-4" />
|
||||||
<h2 className="text-xl sm:text-2xl font-bold text-gray-800 mb-2">
|
<h2 className="text-xl sm:text-2xl font-bold text-gray-800 dark:text-white mb-2">
|
||||||
{translate('::' + 'App.Videoroom.Host')}
|
{translate('::' + 'App.Videoroom.Host')}
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-gray-600 text-sm sm:text-base">
|
<p className="text-gray-600 dark:text-gray-300 text-sm sm:text-base">
|
||||||
{translate('::' + 'App.Videoroom.HostDescription')}
|
{translate('::' + 'App.Videoroom.HostDescription')}
|
||||||
</p>
|
</p>
|
||||||
</motion.button>
|
</motion.button>
|
||||||
|
|
@ -60,13 +60,13 @@ const Dashboard: React.FC = () => {
|
||||||
whileHover={{ scale: 1.05 }}
|
whileHover={{ scale: 1.05 }}
|
||||||
whileTap={{ scale: 0.95 }}
|
whileTap={{ scale: 0.95 }}
|
||||||
onClick={() => handleRoleSelect('student')}
|
onClick={() => handleRoleSelect('student')}
|
||||||
className="bg-white rounded-lg shadow-lg p-6 sm:p-8 hover:shadow-xl transition-all duration-300 border-2 border-transparent hover:border-green-500"
|
className="bg-white dark:bg-gray-800 rounded-lg shadow-lg p-6 sm:p-8 hover:shadow-xl transition-all duration-300 border-2 border-transparent hover:border-green-500 dark:hover:border-green-400"
|
||||||
>
|
>
|
||||||
<FaUsers size={48} className="mx-auto text-green-600 mb-4 sm:mb-4" />
|
<FaUsers size={48} className="mx-auto text-green-600 mb-4 sm:mb-4" />
|
||||||
<h2 className="text-xl sm:text-2xl font-bold text-gray-800 mb-2">
|
<h2 className="text-xl sm:text-2xl font-bold text-gray-800 dark:text-white mb-2">
|
||||||
{translate('::' + 'App.Videoroom.Participant')}
|
{translate('::' + 'App.Videoroom.Participant')}
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-gray-600 text-sm sm:text-base">
|
<p className="text-gray-600 dark:text-gray-300 text-sm sm:text-base">
|
||||||
{translate('::' + 'App.Videoroom.ParticipantDescription')}
|
{translate('::' + 'App.Videoroom.ParticipantDescription')}
|
||||||
</p>
|
</p>
|
||||||
</motion.button>
|
</motion.button>
|
||||||
|
|
@ -75,13 +75,13 @@ const Dashboard: React.FC = () => {
|
||||||
whileHover={{ scale: 1.05 }}
|
whileHover={{ scale: 1.05 }}
|
||||||
whileTap={{ scale: 0.95 }}
|
whileTap={{ scale: 0.95 }}
|
||||||
onClick={() => handleRoleSelect('observer')}
|
onClick={() => handleRoleSelect('observer')}
|
||||||
className="bg-white rounded-lg shadow-lg p-6 sm:p-8 hover:shadow-xl transition-all duration-300 border-2 border-transparent hover:border-purple-500 md:col-span-2 lg:col-span-1"
|
className="bg-white dark:bg-gray-800 rounded-lg shadow-lg p-6 sm:p-8 hover:shadow-xl transition-all duration-300 border-2 border-transparent hover:border-purple-500 dark:hover:border-purple-400 md:col-span-2 lg:col-span-1"
|
||||||
>
|
>
|
||||||
<FaEye size={48} className="mx-auto text-purple-600 mb-4 sm:mb-4" />
|
<FaEye size={48} className="mx-auto text-purple-600 mb-4 sm:mb-4" />
|
||||||
<h2 className="text-xl sm:text-2xl font-bold text-gray-800 mb-2">
|
<h2 className="text-xl sm:text-2xl font-bold text-gray-800 dark:text-white mb-2">
|
||||||
{translate('::' + 'App.Videoroom.Observer')}
|
{translate('::' + 'App.Videoroom.Observer')}
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-gray-600 text-sm sm:text-base">
|
<p className="text-gray-600 dark:text-gray-300 text-sm sm:text-base">
|
||||||
{translate('::' + 'App.Videoroom.ObserverDescription')}
|
{translate('::' + 'App.Videoroom.ObserverDescription')}
|
||||||
</p>
|
</p>
|
||||||
</motion.button>
|
</motion.button>
|
||||||
|
|
|
||||||
|
|
@ -274,19 +274,20 @@ const RoomList: React.FC = () => {
|
||||||
<div
|
<div
|
||||||
className={classNames(
|
className={classNames(
|
||||||
'flex items-center gap-2 pb-1 border-b',
|
'flex items-center gap-2 pb-1 border-b',
|
||||||
mode === 'light' ? 'border-gray-200' : 'border-neutral-700',
|
mode === 'light' ? 'border-gray-200' : 'border-neutral-700 dark:border-gray-700',
|
||||||
|
'bg-white dark:bg-gray-900'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<FcVideoCall size={24} />
|
<FcVideoCall size={24} />
|
||||||
<h4 className="text-sm font-medium">{translate('::App.Videoroom.List')}</h4>
|
<h4 className="text-sm font-medium text-gray-900 dark:text-white">{translate('::App.Videoroom.List')}</h4>
|
||||||
|
|
||||||
<div className="flex gap-1 ml-auto items-center">
|
<div className="flex gap-1 ml-auto items-center">
|
||||||
<div className="flex-1 relative">
|
<div className="flex-1 relative">
|
||||||
<FaSearch className="absolute left-3 top-1/2 transform -translate-y-1/2 w-5 h-5 text-slate-400" />
|
<FaSearch className="absolute left-3 top-1/2 transform -translate-y-1/2 w-5 h-5 text-slate-400 dark:text-gray-500" />
|
||||||
<Input
|
<Input
|
||||||
size="sm"
|
size="sm"
|
||||||
type="text"
|
type="text"
|
||||||
className="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
className="w-full pl-10 pr-4 py-2 border border-gray-300 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||||
placeholder="Search..."
|
placeholder="Search..."
|
||||||
value={searchTerm}
|
value={searchTerm}
|
||||||
onChange={(e) => setSearchTerm(e.target.value)}
|
onChange={(e) => setSearchTerm(e.target.value)}
|
||||||
|
|
@ -352,11 +353,11 @@ const RoomList: React.FC = () => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Scheduled Classes */}
|
{/* Scheduled Classes */}
|
||||||
<div className="bg-white rounded-lg shadow-md">
|
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-md">
|
||||||
{videoList.length === 0 ? (
|
{videoList.length === 0 ? (
|
||||||
<div className="text-center py-12">
|
<div className="text-center py-12">
|
||||||
<FaCalendarAlt size={48} className="mx-auto text-gray-400 mb-4" />
|
<FaCalendarAlt size={48} className="mx-auto text-gray-400 dark:text-gray-600 mb-4" />
|
||||||
<p className="text-sm text-gray-400 text-center py-4">
|
<p className="text-sm text-gray-400 dark:text-gray-500 text-center py-4">
|
||||||
{translate('::App.Videoroom.NoScheduledRooms') ||
|
{translate('::App.Videoroom.NoScheduledRooms') ||
|
||||||
'No scheduled classes found. Please create a new class.'}
|
'No scheduled classes found. Please create a new class.'}
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -379,17 +380,17 @@ const RoomList: React.FC = () => {
|
||||||
initial={{ opacity: 0, y: 10 }}
|
initial={{ opacity: 0, y: 10 }}
|
||||||
animate={{ opacity: 1, y: 0 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
transition={{ delay: index * 0.05 }}
|
transition={{ delay: index * 0.05 }}
|
||||||
className={`bg-white border border-gray-100 border-l-4 ${accentColor} rounded-xl shadow-sm hover:shadow-md transition-all duration-200`}
|
className={`bg-white dark:bg-gray-900 border border-gray-100 dark:border-gray-700 border-l-4 ${accentColor.replace('border-l-', 'dark:border-l-')} rounded-xl shadow-sm hover:shadow-md transition-all duration-200`}
|
||||||
>
|
>
|
||||||
{/* Card Header */}
|
{/* Card Header */}
|
||||||
<div className="flex flex-col sm:flex-row sm:items-start sm:justify-between gap-3 p-4 pb-3">
|
<div className="flex flex-col sm:flex-row sm:items-start sm:justify-between gap-3 p-4 pb-3">
|
||||||
<div className="flex flex-col gap-1 min-w-0">
|
<div className="flex flex-col gap-1 min-w-0">
|
||||||
<div className="flex items-center gap-2 flex-wrap">
|
<div className="flex items-center gap-2 flex-wrap">
|
||||||
<h3 className="text-sm font-semibold text-gray-900 truncate">
|
<h3 className="text-sm font-semibold text-gray-900 dark:text-white truncate">
|
||||||
{classSession.name}
|
{classSession.name}
|
||||||
</h3>
|
</h3>
|
||||||
<span
|
<span
|
||||||
className={`inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium ${className}`}
|
className={`inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium ${className} dark:bg-opacity-80`}
|
||||||
>
|
>
|
||||||
{status}
|
{status}
|
||||||
</span>
|
</span>
|
||||||
|
|
@ -397,12 +398,12 @@ const RoomList: React.FC = () => {
|
||||||
{(classSession.subject || classSession.description) && (
|
{(classSession.subject || classSession.description) && (
|
||||||
<div className="flex flex-col gap-0.5">
|
<div className="flex flex-col gap-0.5">
|
||||||
{classSession.subject && (
|
{classSession.subject && (
|
||||||
<span className="text-xs font-medium text-indigo-600">
|
<span className="text-xs font-medium text-indigo-600 dark:text-indigo-400">
|
||||||
{classSession.subject}
|
{classSession.subject}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{classSession.description && (
|
{classSession.description && (
|
||||||
<p className="text-xs text-gray-400 line-clamp-1">
|
<p className="text-xs text-gray-400 dark:text-gray-500 line-clamp-1">
|
||||||
{classSession.description}
|
{classSession.description}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
|
@ -523,13 +524,13 @@ const RoomList: React.FC = () => {
|
||||||
>
|
>
|
||||||
<form
|
<form
|
||||||
onSubmit={showCreateModal ? handleCreateClass : handleEditClass}
|
onSubmit={showCreateModal ? handleCreateClass : handleEditClass}
|
||||||
className="flex flex-col h-full"
|
className="flex flex-col h-full p-0"
|
||||||
>
|
>
|
||||||
<Dialog.Body className="flex flex-col gap-2 overflow-hidden">
|
<Dialog.Body className="flex flex-col gap-2 overflow-hidden bg-white dark:bg-gray-800">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="flex items-center gap-3 border-b pb-3 flex-shrink-0">
|
<div className="flex items-center gap-3 border-b pb-3 flex-shrink-0 border-gray-200 dark:border-gray-700">
|
||||||
<FcVideoCall className="text-2xl" />
|
<FcVideoCall className="text-2xl" />
|
||||||
<h5 className="font-bold">
|
<h5 className="font-bold text-gray-900 dark:text-white">
|
||||||
{showCreateModal
|
{showCreateModal
|
||||||
? translate('::App.Videoroom.CreateRoom') || 'Yeni Oda Oluştur'
|
? translate('::App.Videoroom.CreateRoom') || 'Yeni Oda Oluştur'
|
||||||
: translate('::App.Videoroom.EditRoom') || 'Odayı Düzenle'}
|
: translate('::App.Videoroom.EditRoom') || 'Odayı Düzenle'}
|
||||||
|
|
@ -538,7 +539,7 @@ const RoomList: React.FC = () => {
|
||||||
|
|
||||||
<div className="flex-1 overflow-y-auto p-1 space-y-4">
|
<div className="flex-1 overflow-y-auto p-1 space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||||
{translate('::App.Listform.ListformField.RoomName') || 'Room Name'} *
|
{translate('::App.Listform.ListformField.RoomName') || 'Room Name'} *
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
|
|
@ -547,7 +548,7 @@ const RoomList: React.FC = () => {
|
||||||
autoFocus={showCreateModal}
|
autoFocus={showCreateModal}
|
||||||
value={videoroom.name}
|
value={videoroom.name}
|
||||||
onChange={(e) => setVideoroom({ ...videoroom, name: e.target.value })}
|
onChange={(e) => setVideoroom({ ...videoroom, name: e.target.value })}
|
||||||
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-white placeholder-gray-400 dark:placeholder-gray-500 focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||||
placeholder={
|
placeholder={
|
||||||
translate('::App.Listform.ListformField.RoomNamePlaceholder') ||
|
translate('::App.Listform.ListformField.RoomNamePlaceholder') ||
|
||||||
'Enter room name...'
|
'Enter room name...'
|
||||||
|
|
@ -556,14 +557,14 @@ const RoomList: React.FC = () => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||||
{translate('::App.Listform.ListformField.Description') || 'Description'}
|
{translate('::App.Listform.ListformField.Description') || 'Description'}
|
||||||
</label>
|
</label>
|
||||||
<textarea
|
<textarea
|
||||||
value={videoroom.description}
|
value={videoroom.description}
|
||||||
onChange={(e) => setVideoroom({ ...videoroom, description: e.target.value })}
|
onChange={(e) => setVideoroom({ ...videoroom, description: e.target.value })}
|
||||||
rows={3}
|
rows={3}
|
||||||
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-white placeholder-gray-400 dark:placeholder-gray-500 focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||||
placeholder={
|
placeholder={
|
||||||
translate('::App.Listform.ListformField.DescriptionPlaceholder') ||
|
translate('::App.Listform.ListformField.DescriptionPlaceholder') ||
|
||||||
'Ders hakkında kısa açıklama...'
|
'Ders hakkında kısa açıklama...'
|
||||||
|
|
@ -573,14 +574,14 @@ const RoomList: React.FC = () => {
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||||
{translate('::App.Listform.ListformField.Subject') || 'Subject'}
|
{translate('::App.Listform.ListformField.Subject') || 'Subject'}
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={videoroom.subject}
|
value={videoroom.subject}
|
||||||
onChange={(e) => setVideoroom({ ...videoroom, subject: e.target.value })}
|
onChange={(e) => setVideoroom({ ...videoroom, subject: e.target.value })}
|
||||||
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-white placeholder-gray-400 dark:placeholder-gray-500 focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||||
placeholder={
|
placeholder={
|
||||||
translate('::App.Listform.ListformField.SubjectPlaceholder') ||
|
translate('::App.Listform.ListformField.SubjectPlaceholder') ||
|
||||||
'E.g. Math, Physics, Chemistry'
|
'E.g. Math, Physics, Chemistry'
|
||||||
|
|
@ -589,7 +590,7 @@ const RoomList: React.FC = () => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||||
{translate('::App.Listform.ListformField.StartDateTime') ||
|
{translate('::App.Listform.ListformField.StartDateTime') ||
|
||||||
'Start Date and Time'}{' '}
|
'Start Date and Time'}{' '}
|
||||||
*
|
*
|
||||||
|
|
@ -608,14 +609,14 @@ const RoomList: React.FC = () => {
|
||||||
scheduledStartTime: e.target.value,
|
scheduledStartTime: e.target.value,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||||
{translate('::App.Listform.ListformField.Duration') || 'Duration'} (
|
{translate('::App.Listform.ListformField.Duration') || 'Duration'} (
|
||||||
{translate('::App.Listform.ListformField.Minutes') || 'minutes'})
|
{translate('::App.Listform.ListformField.Minutes') || 'minutes'})
|
||||||
</label>
|
</label>
|
||||||
|
|
@ -630,12 +631,12 @@ const RoomList: React.FC = () => {
|
||||||
duration: parseInt(e.target.value),
|
duration: parseInt(e.target.value),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||||
{translate('::App.Listform.ListformField.MaxParticipants') ||
|
{translate('::App.Listform.ListformField.MaxParticipants') ||
|
||||||
'Maximum Participants'}
|
'Maximum Participants'}
|
||||||
</label>
|
</label>
|
||||||
|
|
@ -650,20 +651,20 @@ const RoomList: React.FC = () => {
|
||||||
maxParticipants: parseInt(e.target.value),
|
maxParticipants: parseInt(e.target.value),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Sınıf Ayarları */}
|
{/* Sınıf Ayarları */}
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-lg font-semibold text-gray-800 mb-2">
|
<h3 className="text-lg font-semibold text-gray-800 dark:text-white mb-2">
|
||||||
{translate('::App.Videoroom.RoomSettings') || 'Sınıf Ayarları'}
|
{translate('::App.Videoroom.RoomSettings') || 'Sınıf Ayarları'}
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4 sm:gap-6">
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4 sm:gap-6">
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<h4 className="font-medium text-gray-700">
|
<h4 className="font-medium text-gray-700 dark:text-gray-300">
|
||||||
{translate('::App.Videoroom.ParticipantPermissions') ||
|
{translate('::App.Videoroom.ParticipantPermissions') ||
|
||||||
'Katılımcı İzinleri'}
|
'Katılımcı İzinleri'}
|
||||||
</h4>{' '}
|
</h4>{' '}
|
||||||
|
|
@ -680,9 +681,9 @@ const RoomList: React.FC = () => {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
className="rounded"
|
className="rounded border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 text-blue-600 focus:ring-blue-500"
|
||||||
/>
|
/>
|
||||||
<span className="text-sm">
|
<span className="text-sm text-gray-700 dark:text-gray-300">
|
||||||
{translate('::App.Videoroom.AllowHandRaise') || 'Parmak kaldırma izni'}
|
{translate('::App.Videoroom.AllowHandRaise') || 'Parmak kaldırma izni'}
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
|
|
@ -699,9 +700,9 @@ const RoomList: React.FC = () => {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
className="rounded"
|
className="rounded border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 text-blue-600 focus:ring-blue-500"
|
||||||
/>
|
/>
|
||||||
<span className="text-sm">
|
<span className="text-sm text-gray-700 dark:text-gray-300">
|
||||||
{translate('::App.Videoroom.AllowStudentChat') || 'Öğrenci sohbet izni'}
|
{translate('::App.Videoroom.AllowStudentChat') || 'Öğrenci sohbet izni'}
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
|
|
@ -718,9 +719,9 @@ const RoomList: React.FC = () => {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
className="rounded"
|
className="rounded border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 text-blue-600 focus:ring-blue-500"
|
||||||
/>
|
/>
|
||||||
<span className="text-sm">
|
<span className="text-sm text-gray-700 dark:text-gray-300">
|
||||||
{translate('::App.Videoroom.AllowPrivateMessages') || 'Özel mesaj izni'}
|
{translate('::App.Videoroom.AllowPrivateMessages') || 'Özel mesaj izni'}
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
|
|
@ -737,9 +738,9 @@ const RoomList: React.FC = () => {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
className="rounded"
|
className="rounded border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 text-blue-600 focus:ring-blue-500"
|
||||||
/>
|
/>
|
||||||
<span className="text-sm">
|
<span className="text-sm text-gray-700 dark:text-gray-300">
|
||||||
{translate('::App.Videoroom.AllowStudentScreenShare') ||
|
{translate('::App.Videoroom.AllowStudentScreenShare') ||
|
||||||
'Öğrenci ekran paylaşımı'}
|
'Öğrenci ekran paylaşımı'}
|
||||||
</span>
|
</span>
|
||||||
|
|
@ -747,12 +748,12 @@ const RoomList: React.FC = () => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<h4 className="font-medium text-gray-700">
|
<h4 className="font-medium text-gray-700 dark:text-gray-300">
|
||||||
{translate('::App.Videoroom.DefaultSettings') || 'Varsayılan Ayarlar'}
|
{translate('::App.Videoroom.DefaultSettings') || 'Varsayılan Ayarlar'}
|
||||||
</h4>
|
</h4>
|
||||||
|
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
||||||
{translate('::App.Videoroom.DefaultMicrophoneState') ||
|
{translate('::App.Videoroom.DefaultMicrophoneState') ||
|
||||||
'Varsayılan mikrofon durumu'}
|
'Varsayılan mikrofon durumu'}
|
||||||
</label>
|
</label>
|
||||||
|
|
@ -767,7 +768,7 @@ const RoomList: React.FC = () => {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
className="border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
className="border border-gray-300 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||||
>
|
>
|
||||||
<option value="muted">
|
<option value="muted">
|
||||||
{translate('::App.Videoroom.MicrophoneMuted') || 'Kapalı'}
|
{translate('::App.Videoroom.MicrophoneMuted') || 'Kapalı'}
|
||||||
|
|
@ -779,7 +780,7 @@ const RoomList: React.FC = () => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
||||||
{translate('::App.Videoroom.DefaultCameraState') ||
|
{translate('::App.Videoroom.DefaultCameraState') ||
|
||||||
'Varsayılan kamera durumu'}
|
'Varsayılan kamera durumu'}
|
||||||
</label>
|
</label>
|
||||||
|
|
@ -794,7 +795,7 @@ const RoomList: React.FC = () => {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
className="border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
className="border border-gray-300 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||||
>
|
>
|
||||||
<option value="on">
|
<option value="on">
|
||||||
{translate('::App.Videoroom.CameraOn') || 'Açık'}
|
{translate('::App.Videoroom.CameraOn') || 'Açık'}
|
||||||
|
|
@ -806,7 +807,7 @@ const RoomList: React.FC = () => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
||||||
{translate('::App.Videoroom.DefaultLayout') || 'Varsayılan layout'}
|
{translate('::App.Videoroom.DefaultLayout') || 'Varsayılan layout'}
|
||||||
</label>
|
</label>
|
||||||
<select
|
<select
|
||||||
|
|
@ -820,7 +821,7 @@ const RoomList: React.FC = () => {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
className="border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
className="border border-gray-300 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||||
>
|
>
|
||||||
<option value="grid">
|
<option value="grid">
|
||||||
{translate('::App.Videoroom.LayoutGridView') || 'Izgara Görünümü'}
|
{translate('::App.Videoroom.LayoutGridView') || 'Izgara Görünümü'}
|
||||||
|
|
@ -850,9 +851,9 @@ const RoomList: React.FC = () => {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
className="rounded"
|
className="rounded border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 text-blue-600 focus:ring-blue-500"
|
||||||
/>
|
/>
|
||||||
<span className="text-sm">
|
<span className="text-sm text-gray-700 dark:text-gray-300">
|
||||||
{translate('::App.Videoroom.AutomaticallyMuteNewParticipants') ||
|
{translate('::App.Videoroom.AutomaticallyMuteNewParticipants') ||
|
||||||
'Yeni katılımcıları otomatik sustur'}
|
'Yeni katılımcıları otomatik sustur'}
|
||||||
</span>
|
</span>
|
||||||
|
|
|
||||||
|
|
@ -289,6 +289,7 @@ const Login = () => {
|
||||||
name="userName"
|
name="userName"
|
||||||
placeholder={translate('::Abp.Account.EmailAddress')}
|
placeholder={translate('::Abp.Account.EmailAddress')}
|
||||||
component={Input}
|
component={Input}
|
||||||
|
inputClassName="dark:bg-gray-900 dark:text-gray-100"
|
||||||
/>
|
/>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
|
|
@ -352,12 +353,12 @@ const Login = () => {
|
||||||
<Button block loading={isSubmitting} variant="solid" type="submit">
|
<Button block loading={isSubmitting} variant="solid" type="submit">
|
||||||
{isSubmitting ? 'Signing in...' : 'Sign In'}
|
{isSubmitting ? 'Signing in...' : 'Sign In'}
|
||||||
</Button>
|
</Button>
|
||||||
<div className="mt-4 text-center">
|
{/* <div className="mt-4 text-center">
|
||||||
<span>{translate('::Abp.Account.SignUp.Message')} </span>
|
<span>{translate('::Abp.Account.SignUp.Message')} </span>
|
||||||
<ActionLink to={ROUTES_ENUM.authenticated.register}>
|
<ActionLink to={ROUTES_ENUM.authenticated.register}>
|
||||||
{translate('::Abp.Account.Register')}
|
{translate('::Abp.Account.Register')}
|
||||||
</ActionLink>
|
</ActionLink>
|
||||||
</div>
|
</div> */}
|
||||||
</FormContainer>
|
</FormContainer>
|
||||||
</Form>
|
</Form>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -620,21 +620,21 @@ function ComponentCodeLayout() {
|
||||||
const mainContent = (
|
const mainContent = (
|
||||||
<div className="flex-1 flex flex-col min-h-0 h-full">
|
<div className="flex-1 flex flex-col min-h-0 h-full">
|
||||||
{/* Top Header */}
|
{/* Top Header */}
|
||||||
<div className="bg-white border-b border-gray-200 px-3 py-3">
|
<div className="bg-white dark:bg-gray-900 border-b border-gray-200 dark:border-gray-700 px-3 py-3">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex items-center space-x-4">
|
<div className="flex items-center space-x-4">
|
||||||
<h1 className="text-lg font-semibold text-gray-900">{name}</h1>
|
<h1 className="text-lg font-semibold text-gray-900 dark:text-gray-100">{name}</h1>
|
||||||
<p className="text-xs text-gray-500">{dependencies.join(', ')}</p>
|
<p className="text-xs text-gray-500 dark:text-gray-400">{dependencies.join(', ')}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center space-x-3">
|
<div className="flex items-center space-x-3">
|
||||||
<button
|
<button
|
||||||
onClick={() => setShowPanelManager(true)}
|
onClick={() => setShowPanelManager(true)}
|
||||||
className="flex items-center space-x-2 px-3 py-2 bg-white border border-gray-300 rounded-lg hover:bg-gray-50 transition-colors"
|
className="flex items-center space-x-2 px-3 py-2 bg-white dark:bg-gray-900 border border-gray-300 dark:border-gray-700 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors"
|
||||||
title="Panel Manager"
|
title="Panel Manager"
|
||||||
>
|
>
|
||||||
<FaThLarge className="w-4 h-4 text-gray-600" />
|
<FaThLarge className="w-4 h-4 text-gray-600 dark:text-gray-300" />
|
||||||
<span className="text-sm text-gray-600">Panels</span>
|
<span className="text-sm text-gray-600 dark:text-gray-300">Panels</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -657,7 +657,7 @@ function ComponentCodeLayout() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="flex h-screen bg-gray-50"
|
className="flex h-screen bg-gray-50 dark:bg-gray-950"
|
||||||
onDragOver={handleAppDragOver}
|
onDragOver={handleAppDragOver}
|
||||||
onDrop={(e) => {
|
onDrop={(e) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
|
|
||||||
|
|
@ -189,9 +189,7 @@ export default ${pascalCaseName}Component;`
|
||||||
<div className="h-screen flex items-center justify-center">
|
<div className="h-screen flex items-center justify-center">
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<FaSync className="w-8 h-8 text-blue-500 animate-spin mx-auto mb-3" />
|
<FaSync className="w-8 h-8 text-blue-500 animate-spin mx-auto mb-3" />
|
||||||
<p className="text-slate-600">
|
<p className="text-slate-600">{translate('::App.Loading')}</p>
|
||||||
{translate('::App.Loading')}
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
@ -207,29 +205,21 @@ export default ${pascalCaseName}Component;`
|
||||||
{({ values, touched, errors, isSubmitting, setFieldValue, submitForm, isValid }) => (
|
{({ values, touched, errors, isSubmitting, setFieldValue, submitForm, isValid }) => (
|
||||||
<>
|
<>
|
||||||
{/* Enhanced Header */}
|
{/* Enhanced Header */}
|
||||||
<div className="bg-white shadow-lg border-b border-slate-200 sticky top-0 z-10">
|
<div className="bg-white dark:bg-gray-900 shadow-lg border-b border-slate-200 dark:border-gray-700 sticky top-0 z-10">
|
||||||
<div className="px-4 py-3">
|
<div className="px-1 py-3">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
<Link
|
|
||||||
to={ROUTES_ENUM.protected.saas.developerKit.components}
|
|
||||||
className="flex items-center gap-2 text-slate-600 text-black px-4 py-2 rounded-lg hover:text-slate-700 transition-colors"
|
|
||||||
>
|
|
||||||
<FaArrowLeft className="w-3.5 h-3.5" />
|
|
||||||
{translate('::App.DeveloperKit.ComponentEditor.Back')}
|
|
||||||
</Link>
|
|
||||||
<div className="h-6 w-px bg-slate-300"></div>
|
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div className="bg-gradient-to-r from-blue-500 to-purple-600 p-2 rounded-lg">
|
<div className="bg-gradient-to-r from-blue-500 to-purple-600 p-2 rounded-lg">
|
||||||
<FaCode className="w-5 h-5 text-white" />
|
<FaCode className="w-5 h-5 text-white" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h1 className="font-semibold text-slate-800 text-sm leading-tight">
|
<h1 className="font-semibold text-slate-800 dark:text-gray-100 text-sm leading-tight">
|
||||||
{isEditing
|
{isEditing
|
||||||
? `${translate('::App.DeveloperKit.ComponentEditor.Title.Edit')} - ${values.name || initialValues.name || 'Component'}`
|
? `${translate('::App.DeveloperKit.ComponentEditor.Title.Edit')} - ${values.name || initialValues.name || 'Component'}`
|
||||||
: translate('::App.DeveloperKit.ComponentEditor.Title.Create')}
|
: translate('::App.DeveloperKit.ComponentEditor.Title.Create')}
|
||||||
</h1>
|
</h1>
|
||||||
<p className="text-xs text-slate-500 leading-tight">
|
<p className="text-xs text-slate-500 dark:text-gray-400 leading-tight">
|
||||||
{isEditing ? 'Modify your React component' : 'Create a new React component'}
|
{isEditing ? 'Modify your React component' : 'Create a new React component'}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -238,11 +228,20 @@ export default ${pascalCaseName}Component;`
|
||||||
|
|
||||||
{/* Save Button in Header */}
|
{/* Save Button in Header */}
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
|
<Link
|
||||||
|
to={ROUTES_ENUM.protected.saas.developerKit.components}
|
||||||
|
className="flex items-center gap-2 text-slate-600 dark:text-gray-300 text-black dark:text-white px-4 py-2 rounded-lg hover:text-slate-700 dark:hover:text-gray-100 transition-colors"
|
||||||
|
>
|
||||||
|
<FaArrowLeft className="w-3.5 h-3.5" />
|
||||||
|
{translate('::App.DeveloperKit.ComponentEditor.Back')}
|
||||||
|
</Link>
|
||||||
|
<div className="h-6 w-px bg-slate-300 dark:bg-gray-700"></div>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={submitForm}
|
onClick={submitForm}
|
||||||
disabled={isSubmitting || !values.name.trim() || !isValid}
|
disabled={isSubmitting || !values.name.trim() || !isValid}
|
||||||
className="flex items-center gap-2 bg-emerald-600 text-white px-4 py-2 rounded-lg hover:bg-emerald-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
|
className="flex items-center gap-2 bg-emerald-600 text-white px-4 py-2 rounded-lg hover:bg-emerald-700 dark:hover:bg-emerald-800 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
|
||||||
>
|
>
|
||||||
<FaRegSave className="w-4 h-4" />
|
<FaRegSave className="w-4 h-4" />
|
||||||
{isSubmitting
|
{isSubmitting
|
||||||
|
|
@ -255,14 +254,15 @@ export default ${pascalCaseName}Component;`
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Form className="grid grid-cols-1 lg:grid-cols-3 gap-4 py-3">
|
<Form className="grid grid-cols-1 lg:grid-cols-3 gap-4 py-3">
|
||||||
{/* Left Side - Component Settings */}
|
|
||||||
<div className="space-y-3 col-span-1">
|
<div className="space-y-3 col-span-1">
|
||||||
<div className="bg-white rounded-lg shadow-sm border border-slate-200 p-3">
|
<div className="bg-white dark:bg-gray-900 rounded-lg shadow-sm border border-slate-200 dark:border-gray-700 p-3">
|
||||||
<div className="flex items-center gap-2 mb-3">
|
<div className="flex items-center gap-2 mb-3">
|
||||||
<div className="bg-blue-100 p-1.5 rounded-lg">
|
<div className="bg-blue-100 dark:bg-blue-900/20 p-1.5 rounded-lg">
|
||||||
<FaCog className="w-4 h-4 text-blue-600" />
|
<FaCog className="w-4 h-4 text-blue-600 dark:text-blue-400" />
|
||||||
</div>
|
</div>
|
||||||
<h2 className="text-base font-semibold text-slate-900">Component Settings</h2>
|
<h2 className="text-base font-semibold text-slate-900 dark:text-gray-100">
|
||||||
|
Component Settings
|
||||||
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<FormContainer size="sm">
|
<FormContainer size="sm">
|
||||||
|
|
@ -275,6 +275,7 @@ export default ${pascalCaseName}Component;`
|
||||||
name="name"
|
name="name"
|
||||||
type="text"
|
type="text"
|
||||||
component={Input}
|
component={Input}
|
||||||
|
autoFocus
|
||||||
placeholder="e.g., Button, Card, Modal"
|
placeholder="e.g., Button, Card, Modal"
|
||||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
const newName = e.target.value
|
const newName = e.target.value
|
||||||
|
|
@ -367,24 +368,27 @@ export default ${pascalCaseName}Component;`
|
||||||
<div className="space-y-4 col-span-2">
|
<div className="space-y-4 col-span-2">
|
||||||
{/* Validation Errors */}
|
{/* Validation Errors */}
|
||||||
{validationErrors.length > 0 && (
|
{validationErrors.length > 0 && (
|
||||||
<div className="bg-red-50 border border-red-200 rounded-lg p-3 shadow-sm">
|
<div className="bg-red-50 dark:bg-red-950 border border-red-200 dark:border-red-700 rounded-lg p-3 shadow-sm">
|
||||||
<div className="flex items-start gap-2">
|
<div className="flex items-start gap-2">
|
||||||
<div className="bg-red-100 rounded-full p-1.5">
|
<div className="bg-red-100 dark:bg-red-900 rounded-full p-1.5">
|
||||||
<FaExclamationCircle className="w-4 h-4 text-red-600" />
|
<FaExclamationCircle className="w-4 h-4 text-red-600 dark:text-red-400" />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<h3 className="text-base font-semibold text-red-800 mb-1">
|
<h3 className="text-base font-semibold text-red-800 dark:text-red-300 mb-1">
|
||||||
Validation Issues
|
Validation Issues
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-xs text-red-700 mb-3">
|
<p className="text-xs text-red-700 dark:text-red-400 mb-3">
|
||||||
{validationErrors.length} issue
|
{validationErrors.length} issue
|
||||||
{validationErrors.length !== 1 ? 's' : ''} found in your code
|
{validationErrors.length !== 1 ? 's' : ''} found in your code
|
||||||
</p>
|
</p>
|
||||||
<div className="space-y-1.5 max-h-32 overflow-y-auto">
|
<div className="space-y-1.5 max-h-32 overflow-y-auto">
|
||||||
{validationErrors.slice(0, 5).map((error, index) => (
|
{validationErrors.slice(0, 5).map((error, index) => (
|
||||||
<div key={index} className="bg-white p-2 rounded border border-red-100">
|
<div
|
||||||
<div className="text-xs text-red-800">
|
key={index}
|
||||||
<span className="font-medium bg-red-100 px-1.5 py-0.5 rounded text-xs">
|
className="bg-white dark:bg-gray-900 p-2 rounded border border-red-100 dark:border-red-700"
|
||||||
|
>
|
||||||
|
<div className="text-xs text-red-800 dark:text-red-300">
|
||||||
|
<span className="font-medium bg-red-100 dark:bg-red-900 px-1.5 py-0.5 rounded text-xs">
|
||||||
Line {error.startLineNumber}
|
Line {error.startLineNumber}
|
||||||
</span>
|
</span>
|
||||||
<span className="ml-2">{error.message}</span>
|
<span className="ml-2">{error.message}</span>
|
||||||
|
|
@ -392,7 +396,7 @@ export default ${pascalCaseName}Component;`
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
{validationErrors.length > 5 && (
|
{validationErrors.length > 5 && (
|
||||||
<div className="text-xs text-red-600 italic text-center py-1">
|
<div className="text-xs text-red-600 dark:text-red-400 italic text-center py-1">
|
||||||
... and {validationErrors.length - 5} more issue
|
... and {validationErrors.length - 5} more issue
|
||||||
{validationErrors.length - 5 !== 1 ? 's' : ''}
|
{validationErrors.length - 5 !== 1 ? 's' : ''}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -404,12 +408,14 @@ export default ${pascalCaseName}Component;`
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Component Preview */}
|
{/* Component Preview */}
|
||||||
<div className="bg-white rounded-lg shadow-sm border border-slate-200 p-3">
|
<div className="bg-white dark:bg-gray-900 rounded-lg shadow-sm border border-slate-200 dark:border-gray-700 p-3">
|
||||||
<div className="flex items-center gap-2 mb-3">
|
<div className="flex items-center gap-2 mb-3">
|
||||||
<div className="bg-purple-100 p-1.5 rounded-lg">
|
<div className="bg-purple-100 dark:bg-purple-900/20 p-1.5 rounded-lg">
|
||||||
<FaEye className="w-4 h-4 text-purple-600" />
|
<FaEye className="w-4 h-4 text-purple-600 dark:text-purple-400" />
|
||||||
</div>
|
</div>
|
||||||
<h2 className="text-base font-semibold text-slate-900">Preview</h2>
|
<h2 className="text-base font-semibold text-slate-900 dark:text-gray-100">
|
||||||
|
Preview
|
||||||
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
<ComponentPreview componentName={values.name} />
|
<ComponentPreview componentName={values.name} />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -107,15 +107,15 @@ const ComponentManager: React.FC = () => {
|
||||||
placeholder={translate('::App.DeveloperKit.Component.SearchPlaceholder')}
|
placeholder={translate('::App.DeveloperKit.Component.SearchPlaceholder')}
|
||||||
value={searchTerm}
|
value={searchTerm}
|
||||||
onChange={(e) => setSearchTerm(e.target.value)}
|
onChange={(e) => setSearchTerm(e.target.value)}
|
||||||
className="w-full pl-10 pr-4 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
className="w-full pl-10 pr-4 py-2 border border-slate-300 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<FaFilter className="w-5 h-5 text-slate-500" />
|
<FaFilter className="w-5 h-5 text-slate-500 dark:text-gray-400" />
|
||||||
<select
|
<select
|
||||||
value={filterActive}
|
value={filterActive}
|
||||||
onChange={(e) => setFilterActive(e.target.value as 'all' | 'active' | 'inactive')}
|
onChange={(e) => setFilterActive(e.target.value as 'all' | 'active' | 'inactive')}
|
||||||
className="px-3 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
className="px-3 py-2 border border-slate-300 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||||
>
|
>
|
||||||
<option value="all">{translate('::App.DeveloperKit.Component.Filter.All')}</option>
|
<option value="all">{translate('::App.DeveloperKit.Component.Filter.All')}</option>
|
||||||
<option value="active">
|
<option value="active">
|
||||||
|
|
@ -129,7 +129,7 @@ const ComponentManager: React.FC = () => {
|
||||||
<div>
|
<div>
|
||||||
<Link
|
<Link
|
||||||
to={ROUTES_ENUM.protected.saas.developerKit.componentsNew}
|
to={ROUTES_ENUM.protected.saas.developerKit.componentsNew}
|
||||||
className="flex items-center gap-2 bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700 transition-colors"
|
className="flex items-center gap-2 bg-blue-600 dark:bg-blue-700 text-white px-4 py-2 rounded-lg hover:bg-blue-700 dark:hover:bg-blue-800 transition-colors"
|
||||||
>
|
>
|
||||||
<FaPlus className="w-4 h-4" />
|
<FaPlus className="w-4 h-4" />
|
||||||
{translate('::App.DeveloperKit.Component.New')}
|
{translate('::App.DeveloperKit.Component.New')}
|
||||||
|
|
@ -149,22 +149,22 @@ const ComponentManager: React.FC = () => {
|
||||||
{filteredComponents.map((component) => (
|
{filteredComponents.map((component) => (
|
||||||
<div
|
<div
|
||||||
key={component.id}
|
key={component.id}
|
||||||
className="bg-white rounded-lg border border-slate-200 shadow-sm hover:shadow-md transition-shadow"
|
className="bg-white dark:bg-gray-900 rounded-lg border border-slate-200 dark:border-gray-700 shadow-sm hover:shadow-md transition-shadow"
|
||||||
>
|
>
|
||||||
<div className="p-6">
|
<div className="p-6">
|
||||||
<div className="flex items-start justify-between">
|
<div className="flex items-start justify-between">
|
||||||
{/* Sol taraf */}
|
{/* Sol taraf */}
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<div className="flex items-center gap-2 mb-1">
|
<div className="flex items-center gap-2 mb-1">
|
||||||
<h3 className="text-lg font-semibold text-slate-900">{component.name}</h3>
|
<h3 className="text-lg font-semibold text-slate-900 dark:text-gray-100">{component.name}</h3>
|
||||||
<div
|
<div
|
||||||
className={`w-2 h-2 rounded-full ${
|
className={`w-2 h-2 rounded-full ${
|
||||||
component.isActive ? 'bg-green-500' : 'bg-slate-300'
|
component.isActive ? 'bg-green-500' : 'bg-slate-300 dark:bg-gray-700'
|
||||||
}`}
|
}`}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p className="text-slate-600 text-sm mb-3">
|
<p className="text-slate-600 dark:text-gray-300 text-sm mb-3">
|
||||||
{(() => {
|
{(() => {
|
||||||
try {
|
try {
|
||||||
const parsed = JSON.parse(component.dependencies ?? '[]')
|
const parsed = JSON.parse(component.dependencies ?? '[]')
|
||||||
|
|
@ -178,13 +178,13 @@ const ComponentManager: React.FC = () => {
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{component.description && (
|
{component.description && (
|
||||||
<p className="text-slate-600 text-sm mb-3">{component.description}</p>
|
<p className="text-slate-600 dark:text-gray-300 text-sm mb-3">{component.description}</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Sağ taraf */}
|
{/* Sağ taraf */}
|
||||||
{component.lastModificationTime && (
|
{component.lastModificationTime && (
|
||||||
<div className="flex items-center gap-1 text-xs text-slate-500 ml-4 whitespace-nowrap">
|
<div className="flex items-center gap-1 text-xs text-slate-500 dark:text-gray-400 ml-4 whitespace-nowrap">
|
||||||
<FaCalendarAlt className="w-3 h-3" />
|
<FaCalendarAlt className="w-3 h-3" />
|
||||||
<span>
|
<span>
|
||||||
{new Date(component.lastModificationTime).toLocaleDateString() ?? ''}
|
{new Date(component.lastModificationTime).toLocaleDateString() ?? ''}
|
||||||
|
|
@ -196,24 +196,24 @@ const ComponentManager: React.FC = () => {
|
||||||
{/* Props Preview */}
|
{/* Props Preview */}
|
||||||
{component.props && (
|
{component.props && (
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<p className="text-xs font-medium text-slate-700 mb-1">
|
<p className="text-xs font-medium text-slate-700 dark:text-gray-400 mb-1">
|
||||||
{translate('::App.DeveloperKit.Component.PropsLabel')}
|
{translate('::App.DeveloperKit.Component.PropsLabel')}
|
||||||
</p>
|
</p>
|
||||||
<code className="text-xs bg-slate-100 text-slate-600 px-2 py-1 rounded">
|
<code className="text-xs bg-slate-100 dark:bg-gray-800 text-slate-600 dark:text-gray-300 px-2 py-1 rounded">
|
||||||
{component.props}
|
{component.props}
|
||||||
</code>
|
</code>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Actions */}
|
{/* Actions */}
|
||||||
<div className="flex items-center justify-between pt-2 border-t border-slate-100">
|
<div className="flex items-center justify-between pt-2 border-t border-slate-100 dark:border-gray-700">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<button
|
<button
|
||||||
onClick={() => handleToggleActive(component.id, !component.isActive)}
|
onClick={() => handleToggleActive(component.id, !component.isActive)}
|
||||||
className={`flex items-center gap-1 px-2 py-1 rounded text-xs font-medium transition-colors ${
|
className={`flex items-center gap-1 px-2 py-1 rounded text-xs font-medium transition-colors ${
|
||||||
component.isActive
|
component.isActive
|
||||||
? 'bg-green-100 text-green-700 hover:bg-green-200'
|
? 'bg-green-100 dark:bg-green-900/20 text-green-700 dark:text-green-400 hover:bg-green-200 dark:hover:bg-green-800'
|
||||||
: 'bg-slate-100 text-slate-600 hover:bg-slate-200'
|
: 'bg-slate-100 dark:bg-gray-800 text-slate-600 dark:text-gray-400 hover:bg-slate-200 dark:hover:bg-gray-700'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{component.isActive ? (
|
{component.isActive ? (
|
||||||
|
|
@ -236,7 +236,7 @@ const ComponentManager: React.FC = () => {
|
||||||
component.id,
|
component.id,
|
||||||
)}
|
)}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
className="p-2 text-slate-600 hover:text-blue-600 hover:bg-blue-50 rounded transition-colors"
|
className="p-2 text-slate-600 dark:text-gray-400 hover:text-blue-600 dark:hover:text-blue-400 hover:bg-blue-50 dark:hover:bg-blue-900 rounded transition-colors"
|
||||||
title={translate('::App.DeveloperKit.Component.Edit')}
|
title={translate('::App.DeveloperKit.Component.Edit')}
|
||||||
>
|
>
|
||||||
<FaRegEdit className="w-4 h-4" />
|
<FaRegEdit className="w-4 h-4" />
|
||||||
|
|
@ -246,14 +246,14 @@ const ComponentManager: React.FC = () => {
|
||||||
':id',
|
':id',
|
||||||
component.id,
|
component.id,
|
||||||
)}
|
)}
|
||||||
className="p-2 text-slate-600 hover:text-blue-600 hover:bg-blue-50 rounded transition-colors"
|
className="p-2 text-slate-600 dark:text-gray-400 hover:text-blue-600 dark:hover:text-blue-400 hover:bg-blue-50 dark:hover:bg-blue-900 rounded transition-colors"
|
||||||
title={translate('::App.DeveloperKit.Component.View')}
|
title={translate('::App.DeveloperKit.Component.View')}
|
||||||
>
|
>
|
||||||
<FaEye className="w-4 h-4" />
|
<FaEye className="w-4 h-4" />
|
||||||
</Link>
|
</Link>
|
||||||
<button
|
<button
|
||||||
onClick={() => handleDelete(component.id, component.name)}
|
onClick={() => handleDelete(component.id, component.name)}
|
||||||
className="p-2 text-slate-600 hover:text-red-600 hover:bg-red-50 rounded transition-colors"
|
className="p-2 text-slate-600 dark:text-gray-400 hover:text-red-600 dark:hover:text-red-400 hover:bg-red-50 dark:hover:bg-red-900 rounded transition-colors"
|
||||||
title={translate('::App.DeveloperKit.Component.Delete')}
|
title={translate('::App.DeveloperKit.Component.Delete')}
|
||||||
>
|
>
|
||||||
<FaTrashAlt className="w-4 h-4" />
|
<FaTrashAlt className="w-4 h-4" />
|
||||||
|
|
|
||||||
|
|
@ -317,7 +317,7 @@ const CrudEndpointManager: React.FC = () => {
|
||||||
const selectedTableEndpoints = selectedTable ? getEndpointsForTable(selectedTable.tableName) : []
|
const selectedTableEndpoints = selectedTable ? getEndpointsForTable(selectedTable.tableName) : []
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col h-full gap-4">
|
<div className="flex flex-col h-full gap-4 bg-gray-50 dark:bg-gray-950">
|
||||||
<Helmet
|
<Helmet
|
||||||
titleTemplate={`%s | ${APP_NAME}`}
|
titleTemplate={`%s | ${APP_NAME}`}
|
||||||
title={translate('::' + 'App.DeveloperKit.CrudEndpoints')}
|
title={translate('::' + 'App.DeveloperKit.CrudEndpoints')}
|
||||||
|
|
@ -362,17 +362,17 @@ const CrudEndpointManager: React.FC = () => {
|
||||||
|
|
||||||
{/* Main two-panel layout */}
|
{/* Main two-panel layout */}
|
||||||
<div
|
<div
|
||||||
className="flex flex-col gap-4 lg:flex-row"
|
className="flex flex-col gap-4 lg:flex-row bg-gray-50 dark:bg-gray-950"
|
||||||
style={{ minHeight: 400, height: 'calc(100vh - 250px)' }}
|
style={{ minHeight: 400, height: 'calc(100vh - 250px)' }}
|
||||||
>
|
>
|
||||||
{/* Left Panel: Table List */}
|
{/* Left Panel: Table List */}
|
||||||
<div className="w-full lg:w-72 lg:flex-shrink-0 flex flex-col bg-white rounded-lg border border-slate-200 overflow-hidden h-[38vh] min-h-[260px] lg:h-auto lg:min-h-0">
|
<div className="w-full lg:w-72 lg:flex-shrink-0 flex flex-col bg-white dark:bg-gray-900 rounded-lg border border-slate-200 dark:border-gray-700 overflow-hidden h-[38vh] min-h-[260px] lg:h-auto lg:min-h-0">
|
||||||
{/* DataSource selector */}
|
{/* DataSource selector */}
|
||||||
<div className="p-3 border-b border-slate-200 bg-slate-50">
|
<div className="p-3 border-b border-slate-200 dark:border-gray-700 bg-slate-50 dark:bg-gray-800">
|
||||||
<select
|
<select
|
||||||
value={selectedDataSource || ''}
|
value={selectedDataSource || ''}
|
||||||
onChange={(e) => setSelectedDataSource(e.target.value)}
|
onChange={(e) => setSelectedDataSource(e.target.value)}
|
||||||
className="w-full px-2 py-1.5 text-sm border border-slate-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-white"
|
className="w-full px-2 py-1.5 text-sm border border-slate-300 dark:border-gray-700 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100"
|
||||||
>
|
>
|
||||||
{dataSources.map((ds) => (
|
{dataSources.map((ds) => (
|
||||||
<option key={ds.id} value={ds.code ?? ''}>
|
<option key={ds.id} value={ds.code ?? ''}>
|
||||||
|
|
@ -384,7 +384,7 @@ const CrudEndpointManager: React.FC = () => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Search + CRUD filter */}
|
{/* Search + CRUD filter */}
|
||||||
<div className="p-3 border-b border-slate-200 space-y-2">
|
<div className="p-3 border-b border-slate-200 dark:border-gray-700 space-y-2">
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<FaSearch className="absolute left-2.5 top-1/2 -translate-y-1/2 text-slate-400 text-xs" />
|
<FaSearch className="absolute left-2.5 top-1/2 -translate-y-1/2 text-slate-400 text-xs" />
|
||||||
<input
|
<input
|
||||||
|
|
@ -392,10 +392,10 @@ const CrudEndpointManager: React.FC = () => {
|
||||||
placeholder={translate('::App.DeveloperKit.CrudEndpoints.SearchTable')}
|
placeholder={translate('::App.DeveloperKit.CrudEndpoints.SearchTable')}
|
||||||
value={tableSearch}
|
value={tableSearch}
|
||||||
onChange={(e) => setTableSearch(e.target.value)}
|
onChange={(e) => setTableSearch(e.target.value)}
|
||||||
className="w-full pl-7 pr-3 py-1.5 text-sm border border-slate-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
className="w-full pl-7 pr-3 py-1.5 text-sm border border-slate-300 dark:border-gray-700 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex rounded-lg border border-slate-200 overflow-hidden text-xs font-medium">
|
<div className="flex rounded-lg border border-slate-200 dark:border-gray-700 overflow-hidden text-xs font-medium">
|
||||||
{(['all', 'with', 'without'] as const).map((f) => {
|
{(['all', 'with', 'without'] as const).map((f) => {
|
||||||
const labels = {
|
const labels = {
|
||||||
all: `${translate('::App.DeveloperKit.CrudEndpoints.FilterAll')} (${dbTables.length})`,
|
all: `${translate('::App.DeveloperKit.CrudEndpoints.FilterAll')} (${dbTables.length})`,
|
||||||
|
|
@ -414,7 +414,7 @@ const CrudEndpointManager: React.FC = () => {
|
||||||
: f === 'without'
|
: f === 'without'
|
||||||
? 'bg-slate-500 text-white'
|
? 'bg-slate-500 text-white'
|
||||||
: 'bg-blue-500 text-white'
|
: 'bg-blue-500 text-white'
|
||||||
: 'bg-white text-slate-500 hover:bg-slate-50'
|
: 'bg-white dark:bg-gray-900 text-slate-500 dark:text-gray-400 hover:bg-slate-50 dark:hover:bg-gray-800'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{labels[f]}
|
{labels[f]}
|
||||||
|
|
@ -438,7 +438,7 @@ const CrudEndpointManager: React.FC = () => {
|
||||||
) : (
|
) : (
|
||||||
Object.entries(tablesBySchema).map(([schema, tables]) => (
|
Object.entries(tablesBySchema).map(([schema, tables]) => (
|
||||||
<div key={schema}>
|
<div key={schema}>
|
||||||
<div className="px-3 py-1.5 text-xs font-semibold text-slate-400 uppercase tracking-wide bg-slate-50 border-b border-slate-100 sticky top-0">
|
<div className="px-3 py-1.5 text-xs font-semibold text-slate-400 dark:text-gray-400 uppercase tracking-wide bg-slate-50 dark:bg-gray-800 border-b border-slate-100 dark:border-gray-700 sticky top-0">
|
||||||
{schema}
|
{schema}
|
||||||
</div>
|
</div>
|
||||||
{tables.map((table) => {
|
{tables.map((table) => {
|
||||||
|
|
@ -450,8 +450,8 @@ const CrudEndpointManager: React.FC = () => {
|
||||||
<button
|
<button
|
||||||
key={table.fullName}
|
key={table.fullName}
|
||||||
onClick={() => setSelectedTable(table)}
|
onClick={() => setSelectedTable(table)}
|
||||||
className={`w-full flex items-center justify-between px-3 py-1 text-left hover:bg-slate-50 transition-colors border-b border-slate-50 ${
|
className={`w-full flex items-center justify-between px-3 py-1 text-left hover:bg-slate-50 dark:hover:bg-gray-800 transition-colors border-b border-slate-50 dark:border-gray-800 ${
|
||||||
isSelected ? 'bg-blue-50 border-l-2 border-l-blue-500' : ''
|
isSelected ? 'bg-blue-50 dark:bg-blue-900/20 border-l-2 border-l-blue-500 dark:border-l-blue-400' : ''
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-2 min-w-0">
|
<div className="flex items-center gap-2 min-w-0">
|
||||||
|
|
@ -460,14 +460,14 @@ const CrudEndpointManager: React.FC = () => {
|
||||||
hasEndpoints ? 'text-green-500' : 'text-slate-300'
|
hasEndpoints ? 'text-green-500' : 'text-slate-300'
|
||||||
}`}
|
}`}
|
||||||
/>
|
/>
|
||||||
<span className="text-sm text-slate-700 truncate">{table.tableName}</span>
|
<span className="text-sm text-slate-700 dark:text-gray-100 truncate">{table.tableName}</span>
|
||||||
</div>
|
</div>
|
||||||
{hasEndpoints && (
|
{hasEndpoints && (
|
||||||
<span
|
<span
|
||||||
className={`flex-shrink-0 text-xs px-1.5 py-0.5 rounded-full font-medium ${
|
className={`flex-shrink-0 text-xs px-1.5 py-0.5 rounded-full font-medium ${
|
||||||
active > 0
|
active > 0
|
||||||
? 'bg-green-100 text-green-700'
|
? 'bg-green-100 dark:bg-green-900/20 text-green-700 dark:text-green-400'
|
||||||
: 'bg-slate-100 text-slate-500'
|
: 'bg-slate-100 dark:bg-gray-800 text-slate-500 dark:text-gray-400'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{active}/{total}
|
{active}/{total}
|
||||||
|
|
@ -486,33 +486,33 @@ const CrudEndpointManager: React.FC = () => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Right Panel: Endpoint Management */}
|
{/* Right Panel: Endpoint Management */}
|
||||||
<div className="flex-1 min-w-0 flex flex-col bg-white rounded-lg border border-slate-200 overflow-hidden min-h-0">
|
<div className="flex-1 min-w-0 flex flex-col bg-white dark:bg-gray-900 rounded-lg border border-slate-200 dark:border-gray-700 overflow-hidden min-h-0">
|
||||||
{!selectedTable ? (
|
{!selectedTable ? (
|
||||||
<div className="flex-1 flex flex-col items-center justify-center text-slate-400 p-8">
|
<div className="flex-1 flex flex-col items-center justify-center text-slate-400 dark:text-gray-500 p-8">
|
||||||
<FaDatabase className="text-4xl mb-3 text-slate-200" />
|
<FaDatabase className="text-4xl mb-3 text-slate-200 dark:text-gray-700" />
|
||||||
<p className="text-base font-medium">{translate('::App.DeveloperKit.CrudEndpoints.SelectTablePrompt')}</p>
|
<p className="text-base font-medium dark:text-gray-300">{translate('::App.DeveloperKit.CrudEndpoints.SelectTablePrompt')}</p>
|
||||||
<p className="text-sm mt-1">
|
<p className="text-sm mt-1 dark:text-gray-400">
|
||||||
{translate('::App.DeveloperKit.CrudEndpoints.SelectTableDescription')}
|
{translate('::App.DeveloperKit.CrudEndpoints.SelectTableDescription')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex flex-col h-full overflow-hidden">
|
<div className="flex flex-col h-full overflow-hidden">
|
||||||
{/* Table header */}
|
{/* Table header */}
|
||||||
<div className="flex flex-col gap-3 p-4 border-b border-slate-200 bg-slate-50 sm:flex-row sm:items-center sm:justify-between">
|
<div className="flex flex-col gap-3 p-4 border-b border-slate-200 dark:border-gray-700 bg-slate-50 dark:bg-gray-800 sm:flex-row sm:items-center sm:justify-between">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div className="bg-blue-100 text-blue-600 p-2 rounded-lg">
|
<div className="bg-blue-100 dark:bg-blue-900/20 text-blue-600 dark:text-blue-400 p-2 rounded-lg">
|
||||||
<FaTable />
|
<FaTable />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h4 className="text-slate-900 dark:text-gray-100">{selectedTable.schemaName}.{selectedTable.tableName}</h4>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
|
||||||
<h4 className="text-slate-900">{selectedTable.schemaName}.{selectedTable.tableName}</h4>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-wrap items-center gap-2">
|
<div className="flex flex-wrap items-center gap-2">
|
||||||
{selectedTableEndpoints.length > 0 && (
|
{selectedTableEndpoints.length > 0 && (
|
||||||
<button
|
<button
|
||||||
onClick={() => handleDeleteAll(selectedTable)}
|
onClick={() => handleDeleteAll(selectedTable)}
|
||||||
disabled={deletingAll === selectedTable.fullName}
|
disabled={deletingAll === selectedTable.fullName}
|
||||||
className="flex items-center gap-2 px-3 py-1.5 text-sm text-red-600 border border-red-200 rounded-lg hover:bg-red-50 disabled:opacity-50 transition-colors"
|
className="flex items-center gap-2 px-3 py-1.5 text-sm text-red-600 border border-red-200 dark:border-red-700 rounded-lg hover:bg-red-50 dark:hover:bg-red-900/20 disabled:opacity-50 transition-colors"
|
||||||
>
|
>
|
||||||
{deletingAll === selectedTable.fullName ? (
|
{deletingAll === selectedTable.fullName ? (
|
||||||
<FaSyncAlt className="animate-spin" />
|
<FaSyncAlt className="animate-spin" />
|
||||||
|
|
@ -525,7 +525,7 @@ const CrudEndpointManager: React.FC = () => {
|
||||||
<button
|
<button
|
||||||
onClick={() => handleGenerate(selectedTable)}
|
onClick={() => handleGenerate(selectedTable)}
|
||||||
disabled={generatingFor === selectedTable.fullName}
|
disabled={generatingFor === selectedTable.fullName}
|
||||||
className="flex items-center gap-2 px-4 py-1.5 text-sm font-medium bg-blue-600 text-white rounded-lg hover:bg-blue-700 disabled:opacity-50 transition-colors"
|
className="flex items-center gap-2 px-4 py-1.5 text-sm font-medium bg-blue-600 text-white rounded-lg hover:bg-blue-700 dark:bg-blue-700 dark:hover:bg-blue-800 disabled:opacity-50 transition-colors"
|
||||||
>
|
>
|
||||||
{generatingFor === selectedTable.fullName ? (
|
{generatingFor === selectedTable.fullName ? (
|
||||||
<FaSyncAlt className="animate-spin" />
|
<FaSyncAlt className="animate-spin" />
|
||||||
|
|
@ -542,10 +542,10 @@ const CrudEndpointManager: React.FC = () => {
|
||||||
{/* Endpoints list */}
|
{/* Endpoints list */}
|
||||||
<div className="flex-1 overflow-y-auto p-4">
|
<div className="flex-1 overflow-y-auto p-4">
|
||||||
{selectedTableEndpoints.length === 0 ? (
|
{selectedTableEndpoints.length === 0 ? (
|
||||||
<div className="flex flex-col items-center justify-center py-16 text-slate-400">
|
<div className="flex flex-col items-center justify-center py-16 text-slate-400 dark:text-gray-500">
|
||||||
<FaBolt className="text-3xl mb-3 text-slate-200" />
|
<FaBolt className="text-3xl mb-3 text-slate-200 dark:text-gray-700" />
|
||||||
<p className="font-medium">{translate('::App.DeveloperKit.CrudEndpoints.NoEndpointsYet')}</p>
|
<p className="font-medium dark:text-gray-300">{translate('::App.DeveloperKit.CrudEndpoints.NoEndpointsYet')}</p>
|
||||||
<p className="text-sm mt-1">
|
<p className="text-sm mt-1 dark:text-gray-400">
|
||||||
{translate('::App.DeveloperKit.CrudEndpoints.ClickToCreate')}
|
{translate('::App.DeveloperKit.CrudEndpoints.ClickToCreate')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -557,10 +557,10 @@ const CrudEndpointManager: React.FC = () => {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={ep.id}
|
key={ep.id}
|
||||||
className="border border-slate-200 rounded-lg overflow-hidden"
|
className="border border-slate-200 dark:border-gray-700 rounded-lg overflow-hidden"
|
||||||
>
|
>
|
||||||
{/* Endpoint row */}
|
{/* Endpoint row */}
|
||||||
<div className="flex items-center gap-3 p-3 bg-white hover:bg-slate-50 transition-colors">
|
<div className="flex items-center gap-3 p-3 bg-white dark:bg-gray-900 hover:bg-slate-50 dark:hover:bg-gray-800 transition-colors">
|
||||||
{/* Toggle */}
|
{/* Toggle */}
|
||||||
<button
|
<button
|
||||||
onClick={() => handleToggle(ep.id)}
|
onClick={() => handleToggle(ep.id)}
|
||||||
|
|
@ -585,7 +585,7 @@ const CrudEndpointManager: React.FC = () => {
|
||||||
<span
|
<span
|
||||||
className={`flex-shrink-0 px-2 py-0.5 text-xs font-bold rounded border ${
|
className={`flex-shrink-0 px-2 py-0.5 text-xs font-bold rounded border ${
|
||||||
METHOD_COLOR[ep.method] ||
|
METHOD_COLOR[ep.method] ||
|
||||||
'bg-slate-100 text-slate-700 border-slate-200'
|
'bg-slate-100 dark:bg-gray-800 text-slate-700 dark:text-gray-300 border-slate-200 dark:border-gray-700'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{ep.method}
|
{ep.method}
|
||||||
|
|
@ -593,10 +593,10 @@ const CrudEndpointManager: React.FC = () => {
|
||||||
|
|
||||||
{/* Operation */}
|
{/* Operation */}
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<span className="font-medium text-slate-800 text-sm">
|
<span className="font-medium text-slate-800 dark:text-gray-100 text-sm">
|
||||||
{ep.operationType}
|
{ep.operationType}
|
||||||
</span>
|
</span>
|
||||||
<code className="ml-3 text-xs text-slate-500 bg-slate-100 px-2 py-0.5 rounded">
|
<code className="ml-3 text-xs text-slate-500 dark:text-gray-400 bg-slate-100 dark:bg-gray-800 px-2 py-0.5 rounded">
|
||||||
{ep.path}
|
{ep.path}
|
||||||
</code>
|
</code>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -604,7 +604,7 @@ const CrudEndpointManager: React.FC = () => {
|
||||||
{/* Expand */}
|
{/* Expand */}
|
||||||
<button
|
<button
|
||||||
onClick={() => setExpandedEndpoint(isExpanded ? null : ep.id)}
|
onClick={() => setExpandedEndpoint(isExpanded ? null : ep.id)}
|
||||||
className="p-1.5 text-slate-400 hover:text-slate-700 transition-colors"
|
className="p-1.5 text-slate-400 dark:text-gray-400 hover:text-slate-700 dark:hover:text-gray-200 transition-colors"
|
||||||
title={translate('::App.DeveloperKit.CrudEndpoints.TestDetails')}
|
title={translate('::App.DeveloperKit.CrudEndpoints.TestDetails')}
|
||||||
>
|
>
|
||||||
{isExpanded ? (
|
{isExpanded ? (
|
||||||
|
|
@ -617,11 +617,11 @@ const CrudEndpointManager: React.FC = () => {
|
||||||
|
|
||||||
{/* Expanded detail + test */}
|
{/* Expanded detail + test */}
|
||||||
{isExpanded && (
|
{isExpanded && (
|
||||||
<div className="border-t border-slate-200 bg-slate-50 p-4 space-y-4">
|
<div className="border-t border-slate-200 dark:border-gray-700 bg-slate-50 dark:bg-gray-800 p-4 space-y-4">
|
||||||
{/* Parameters */}
|
{/* Parameters */}
|
||||||
{getEndpointParameters(ep).length > 0 && (
|
{getEndpointParameters(ep).length > 0 && (
|
||||||
<div>
|
<div>
|
||||||
<p className="text-xs font-semibold text-slate-600 mb-2">
|
<p className="text-xs font-semibold text-slate-600 dark:text-gray-300 mb-2">
|
||||||
{translate('::App.DeveloperKit.CrudEndpoints.Parameters')}
|
{translate('::App.DeveloperKit.CrudEndpoints.Parameters')}
|
||||||
</p>
|
</p>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
|
|
@ -650,7 +650,7 @@ const CrudEndpointManager: React.FC = () => {
|
||||||
},
|
},
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
className="flex-1 px-2 py-1 text-xs border border-slate-300 rounded focus:ring-1 focus:ring-blue-500"
|
className="flex-1 px-2 py-1 text-xs border border-slate-300 dark:border-gray-700 rounded focus:ring-1 focus:ring-blue-500 bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|
@ -661,7 +661,7 @@ const CrudEndpointManager: React.FC = () => {
|
||||||
{/* Request body */}
|
{/* Request body */}
|
||||||
{needsBody(ep) && (
|
{needsBody(ep) && (
|
||||||
<div>
|
<div>
|
||||||
<p className="text-xs font-semibold text-slate-600 mb-2">
|
<p className="text-xs font-semibold text-slate-600 dark:text-gray-300 mb-2">
|
||||||
{translate('::App.DeveloperKit.CrudEndpoints.RequestBody')}
|
{translate('::App.DeveloperKit.CrudEndpoints.RequestBody')}
|
||||||
</p>
|
</p>
|
||||||
<textarea
|
<textarea
|
||||||
|
|
@ -673,7 +673,7 @@ const CrudEndpointManager: React.FC = () => {
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
rows={5}
|
rows={5}
|
||||||
className="w-full px-2 py-1.5 text-xs border border-slate-300 rounded font-mono focus:ring-1 focus:ring-blue-500 bg-white"
|
className="w-full px-2 py-1.5 text-xs border border-slate-300 dark:border-gray-700 rounded font-mono focus:ring-1 focus:ring-blue-500 bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
@ -713,25 +713,25 @@ const CrudEndpointManager: React.FC = () => {
|
||||||
<div
|
<div
|
||||||
className={`rounded-lg border p-3 ${
|
className={`rounded-lg border p-3 ${
|
||||||
testResult.success
|
testResult.success
|
||||||
? 'border-green-200 bg-green-50'
|
? 'border-green-200 dark:border-green-700 bg-green-50 dark:bg-green-900/20'
|
||||||
: 'border-red-200 bg-red-50'
|
: 'border-red-200 dark:border-red-700 bg-red-50 dark:bg-red-900/20'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-2 mb-2">
|
<div className="flex items-center gap-2 mb-2">
|
||||||
{testResult.success ? (
|
{testResult.success ? (
|
||||||
<FaCheckCircle className="text-green-500" />
|
<FaCheckCircle className="text-green-500 dark:text-green-400" />
|
||||||
) : (
|
) : (
|
||||||
<FaExclamationCircle className="text-red-500" />
|
<FaExclamationCircle className="text-red-500 dark:text-red-400" />
|
||||||
)}
|
)}
|
||||||
<span className="text-sm font-medium">
|
<span className="text-sm font-medium">
|
||||||
HTTP {testResult.status}
|
HTTP {testResult.status}
|
||||||
</span>
|
</span>
|
||||||
<span className="text-xs text-slate-500 ml-auto">
|
<span className="text-xs text-slate-500 dark:text-gray-400 ml-auto">
|
||||||
{new Date(testResult.timestamp).toLocaleTimeString()}
|
{new Date(testResult.timestamp).toLocaleTimeString()}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<pre className="text-xs bg-white border border-slate-200 rounded p-2 overflow-x-auto max-h-48">
|
<pre className="text-xs bg-white dark:bg-gray-900 border border-slate-200 dark:border-gray-700 rounded p-2 overflow-x-auto max-h-48 text-gray-900 dark:text-gray-100">
|
||||||
{JSON.stringify(
|
{JSON.stringify(
|
||||||
testResult.success ? testResult.data : testResult.error,
|
testResult.success ? testResult.data : testResult.error,
|
||||||
null,
|
null,
|
||||||
|
|
@ -748,7 +748,7 @@ const CrudEndpointManager: React.FC = () => {
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
className="absolute top-1.5 right-1.5 p-1 text-slate-400 hover:text-slate-700"
|
className="absolute top-1.5 right-1.5 p-1 text-slate-400 dark:text-gray-400 hover:text-slate-700 dark:hover:text-gray-200"
|
||||||
>
|
>
|
||||||
<FaCopy className="text-xs" />
|
<FaCopy className="text-xs" />
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -760,15 +760,15 @@ const CrudEndpointManager: React.FC = () => {
|
||||||
{ep.csharpCode && (
|
{ep.csharpCode && (
|
||||||
<div>
|
<div>
|
||||||
<div className="flex items-center justify-between mb-1">
|
<div className="flex items-center justify-between mb-1">
|
||||||
<p className="text-xs font-semibold text-slate-600">{translate('::App.DeveloperKit.CrudEndpoints.CsharpCode')}</p>
|
<p className="text-xs font-semibold text-slate-600 dark:text-gray-300">{translate('::App.DeveloperKit.CrudEndpoints.CsharpCode')}</p>
|
||||||
<button
|
<button
|
||||||
onClick={() => navigator.clipboard.writeText(ep.csharpCode)}
|
onClick={() => navigator.clipboard.writeText(ep.csharpCode)}
|
||||||
className="text-xs text-slate-400 hover:text-slate-700 flex items-center gap-1"
|
className="text-xs text-slate-400 dark:text-gray-400 hover:text-slate-700 dark:hover:text-gray-200 flex items-center gap-1"
|
||||||
>
|
>
|
||||||
<FaCopy /> {translate('::App.DeveloperKit.CrudEndpoints.Copy')}
|
<FaCopy /> {translate('::App.DeveloperKit.CrudEndpoints.Copy')}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<pre className="text-xs bg-slate-800 text-green-300 rounded-lg p-3 overflow-x-auto max-h-48 font-mono">
|
<pre className="text-xs bg-slate-800 dark:bg-gray-900 text-green-300 rounded-lg p-3 overflow-x-auto max-h-48 font-mono">
|
||||||
{ep.csharpCode}
|
{ep.csharpCode}
|
||||||
</pre>
|
</pre>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -784,7 +784,7 @@ const CrudEndpointManager: React.FC = () => {
|
||||||
|
|
||||||
{/* Footer info */}
|
{/* Footer info */}
|
||||||
{selectedTableEndpoints.length > 0 && (
|
{selectedTableEndpoints.length > 0 && (
|
||||||
<div className="border-t border-slate-200 px-2 py-1 bg-slate-50 flex items-center gap-4 text-xs text-slate-500">
|
<div className="border-t border-slate-200 dark:border-gray-700 px-2 py-1 bg-slate-50 dark:bg-gray-800 flex items-center gap-4 text-xs text-slate-500 dark:text-gray-400">
|
||||||
<span className="flex items-center gap-1">
|
<span className="flex items-center gap-1">
|
||||||
<FaCheckCircle className="text-green-400" />
|
<FaCheckCircle className="text-green-400" />
|
||||||
{selectedTableEndpoints.filter((e) => e.isActive).length} {translate('::App.DeveloperKit.CrudEndpoints.ActiveCount')}
|
{selectedTableEndpoints.filter((e) => e.isActive).length} {translate('::App.DeveloperKit.CrudEndpoints.ActiveCount')}
|
||||||
|
|
|
||||||
|
|
@ -121,7 +121,9 @@ const DynamicServiceEditor: React.FC = () => {
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
setCompileResult({
|
setCompileResult({
|
||||||
success: false,
|
success: false,
|
||||||
errorMessage: error.response?.data?.message || translate('::App.DeveloperKit.DynamicServices.Editor.CompileError'),
|
errorMessage:
|
||||||
|
error.response?.data?.message ||
|
||||||
|
translate('::App.DeveloperKit.DynamicServices.Editor.CompileError'),
|
||||||
compilationTimeMs: 0,
|
compilationTimeMs: 0,
|
||||||
hasWarnings: false,
|
hasWarnings: false,
|
||||||
errors: [],
|
errors: [],
|
||||||
|
|
@ -163,7 +165,9 @@ const DynamicServiceEditor: React.FC = () => {
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
setPublishResult({
|
setPublishResult({
|
||||||
success: false,
|
success: false,
|
||||||
errorMessage: error.response?.data?.message || translate('::App.DeveloperKit.DynamicServices.Editor.PublishError'),
|
errorMessage:
|
||||||
|
error.response?.data?.message ||
|
||||||
|
translate('::App.DeveloperKit.DynamicServices.Editor.PublishError'),
|
||||||
})
|
})
|
||||||
} finally {
|
} finally {
|
||||||
setIsPublishing(false)
|
setIsPublishing(false)
|
||||||
|
|
@ -175,7 +179,9 @@ const DynamicServiceEditor: React.FC = () => {
|
||||||
alert(translate('::App.DeveloperKit.DynamicServices.Editor.CodeCopied'))
|
alert(translate('::App.DeveloperKit.DynamicServices.Editor.CodeCopied'))
|
||||||
}
|
}
|
||||||
|
|
||||||
const pageTitle = id ? translate('::App.DeveloperKit.DynamicServices.Editor.EditTitle') : translate('::App.DeveloperKit.DynamicServices.Editor.NewTitle')
|
const pageTitle = id
|
||||||
|
? translate('::App.DeveloperKit.DynamicServices.Editor.EditTitle')
|
||||||
|
: translate('::App.DeveloperKit.DynamicServices.Editor.NewTitle')
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return (
|
return (
|
||||||
|
|
@ -192,34 +198,38 @@ const DynamicServiceEditor: React.FC = () => {
|
||||||
<Helmet titleTemplate={`%s | ${APP_NAME}`} title={pageTitle} defaultTitle={APP_NAME} />
|
<Helmet titleTemplate={`%s | ${APP_NAME}`} title={pageTitle} defaultTitle={APP_NAME} />
|
||||||
|
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="bg-white shadow-lg border-b border-slate-200 sticky top-0 z-10">
|
<div className="bg-white dark:bg-gray-900 shadow-lg border-b border-slate-200 dark:border-gray-700 sticky top-0 z-10">
|
||||||
<div className="flex items-center justify-between px-4 py-3">
|
<div className="flex items-center justify-between px-1 py-3">
|
||||||
{/* Left: back + icon + title */}
|
{/* Left: back + icon + title */}
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<Link
|
|
||||||
to={ROUTES_ENUM.protected.saas.developerKit.dynamicServices}
|
|
||||||
className="flex items-center gap-2 text-slate-600 text-black px-4 py-2 rounded-lg hover:text-slate-700 transition-colors"
|
|
||||||
>
|
|
||||||
<FaArrowLeft className="w-3.5 h-3.5" />
|
|
||||||
{translate('::App.DeveloperKit.DynamicServices.Editor.BackToServices')}
|
|
||||||
</Link>
|
|
||||||
<div className="h-6 w-px bg-slate-300"></div>
|
|
||||||
<div className="flex items-center justify-center w-9 h-9 rounded-lg bg-gradient-to-r from-blue-500 to-purple-600 text-white shrink-0">
|
<div className="flex items-center justify-center w-9 h-9 rounded-lg bg-gradient-to-r from-blue-500 to-purple-600 text-white shrink-0">
|
||||||
<FaCode className="w-4 h-4" />
|
<FaCode className="w-4 h-4" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h1 className="font-semibold text-slate-800 text-sm leading-tight">{pageTitle}</h1>
|
<h1 className="font-semibold text-slate-800 dark:text-gray-100 text-sm leading-tight">
|
||||||
<p className="text-xs text-slate-500 leading-tight">
|
{pageTitle}
|
||||||
{id ? translate('::App.DeveloperKit.DynamicServices.Editor.EditSubtitle') : translate('::App.DeveloperKit.DynamicServices.Editor.NewSubtitle')}
|
</h1>
|
||||||
|
<p className="text-xs text-slate-500 dark:text-gray-400 leading-tight">
|
||||||
|
{id
|
||||||
|
? translate('::App.DeveloperKit.DynamicServices.Editor.EditSubtitle')
|
||||||
|
: translate('::App.DeveloperKit.DynamicServices.Editor.NewSubtitle')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Right: action buttons + swagger + publish */}
|
{/* Right: action buttons + swagger + publish */}
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
|
<Link
|
||||||
|
to={ROUTES_ENUM.protected.saas.developerKit.dynamicServices}
|
||||||
|
className="flex items-center gap-2 text-slate-600 dark:text-gray-300 text-black dark:text-white px-4 py-2 rounded-lg hover:text-slate-700 dark:hover:text-gray-100 transition-colors"
|
||||||
|
>
|
||||||
|
<FaArrowLeft className="w-3.5 h-3.5" />
|
||||||
|
{translate('::App.DeveloperKit.DynamicServices.Editor.BackToServices')}
|
||||||
|
</Link>
|
||||||
|
<div className="h-6 w-px bg-slate-300 dark:bg-gray-700"></div>
|
||||||
<button
|
<button
|
||||||
onClick={copyCode}
|
onClick={copyCode}
|
||||||
className="flex items-center gap-2 px-4 py-2 border border-slate-300 rounded-lg text-slate-600 hover:bg-slate-50 transition-colors"
|
className="flex items-center gap-2 px-4 py-2 border border-slate-300 dark:border-gray-700 rounded-lg text-slate-600 dark:text-gray-300 hover:bg-slate-50 dark:hover:bg-gray-800 transition-colors"
|
||||||
>
|
>
|
||||||
<FaCopy className="w-3.5 h-3.5" />
|
<FaCopy className="w-3.5 h-3.5" />
|
||||||
{translate('::App.DeveloperKit.DynamicServices.Editor.CopyCode')}
|
{translate('::App.DeveloperKit.DynamicServices.Editor.CopyCode')}
|
||||||
|
|
@ -228,27 +238,31 @@ const DynamicServiceEditor: React.FC = () => {
|
||||||
<button
|
<button
|
||||||
onClick={handleTestCompile}
|
onClick={handleTestCompile}
|
||||||
disabled={isCompiling || !code.trim()}
|
disabled={isCompiling || !code.trim()}
|
||||||
className="flex items-center gap-2 bg-orange-500 text-white px-4 py-2 rounded-lg hover:bg-orange-600 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
|
className="flex items-center gap-2 bg-orange-500 text-white px-4 py-2 rounded-lg hover:bg-orange-600 dark:bg-orange-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
|
||||||
>
|
>
|
||||||
{isCompiling ? (
|
{isCompiling ? (
|
||||||
<FaSpinner className="w-3.5 h-3.5 animate-spin" />
|
<FaSpinner className="w-3.5 h-3.5 animate-spin" />
|
||||||
) : (
|
) : (
|
||||||
<FaPlay className="w-3.5 h-3.5" />
|
<FaPlay className="w-3.5 h-3.5" />
|
||||||
)}
|
)}
|
||||||
{isCompiling ? translate('::App.DeveloperKit.DynamicServices.Editor.Compiling') : translate('::App.DeveloperKit.DynamicServices.Editor.TestCompile')}
|
{isCompiling
|
||||||
|
? translate('::App.DeveloperKit.DynamicServices.Editor.Compiling')
|
||||||
|
: translate('::App.DeveloperKit.DynamicServices.Editor.TestCompile')}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
onClick={handlePublish}
|
onClick={handlePublish}
|
||||||
disabled={isPublishing}
|
disabled={isPublishing}
|
||||||
className="flex items-center gap-2 bg-emerald-600 text-white px-4 py-2 rounded-lg hover:bg-emerald-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
|
className="flex items-center gap-2 bg-emerald-600 text-white px-4 py-2 rounded-lg hover:bg-emerald-700 dark:bg-emerald-800 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
|
||||||
>
|
>
|
||||||
{isPublishing ? (
|
{isPublishing ? (
|
||||||
<FaSpinner className="w-3.5 h-3.5 animate-spin" />
|
<FaSpinner className="w-3.5 h-3.5 animate-spin" />
|
||||||
) : (
|
) : (
|
||||||
<FaSave className="w-3.5 h-3.5" />
|
<FaSave className="w-3.5 h-3.5" />
|
||||||
)}
|
)}
|
||||||
{isPublishing ? translate('::App.DeveloperKit.DynamicServices.Editor.Publishing') : translate('::App.DeveloperKit.DynamicServices.Editor.Publish')}
|
{isPublishing
|
||||||
|
? translate('::App.DeveloperKit.DynamicServices.Editor.Publishing')
|
||||||
|
: translate('::App.DeveloperKit.DynamicServices.Editor.Publish')}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -259,8 +273,8 @@ const DynamicServiceEditor: React.FC = () => {
|
||||||
<div
|
<div
|
||||||
className={`flex items-start gap-3 rounded-lg border px-4 py-3 text-sm ${
|
className={`flex items-start gap-3 rounded-lg border px-4 py-3 text-sm ${
|
||||||
compileResult.success
|
compileResult.success
|
||||||
? 'bg-emerald-50 border-emerald-200 text-emerald-800'
|
? 'bg-emerald-50 dark:bg-emerald-900/20 border-emerald-200 dark:border-emerald-700 text-emerald-800 dark:text-emerald-300'
|
||||||
: 'bg-red-50 border-red-200 text-red-800'
|
: 'bg-red-50 dark:bg-red-900/20 border-red-200 dark:border-red-700 text-red-800 dark:text-red-300'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{compileResult.success ? (
|
{compileResult.success ? (
|
||||||
|
|
@ -270,13 +284,16 @@ const DynamicServiceEditor: React.FC = () => {
|
||||||
)}
|
)}
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<span className="font-medium">
|
<span className="font-medium">
|
||||||
{compileResult.success ? translate('::App.DeveloperKit.DynamicServices.Editor.CompileSuccess') : translate('::App.DeveloperKit.DynamicServices.Editor.CompileFailed')}
|
{compileResult.success
|
||||||
|
? translate('::App.DeveloperKit.DynamicServices.Editor.CompileSuccess')
|
||||||
|
: translate('::App.DeveloperKit.DynamicServices.Editor.CompileFailed')}
|
||||||
</span>
|
</span>
|
||||||
{!compileResult.success && compileResult.errors && compileResult.errors.length > 0 && (
|
{!compileResult.success && compileResult.errors && compileResult.errors.length > 0 && (
|
||||||
<ul className="mt-1 space-y-0.5">
|
<ul className="mt-1 space-y-0.5">
|
||||||
{compileResult.errors.map((e, i) => (
|
{compileResult.errors.map((e, i) => (
|
||||||
<li key={i} className="text-xs font-mono">
|
<li key={i} className="text-xs font-mono">
|
||||||
[{e.code}] {translate('::App.DeveloperKit.DynamicServices.Editor.Line')} {e.line}: {e.message}
|
[{e.code}] {translate('::App.DeveloperKit.DynamicServices.Editor.Line')}{' '}
|
||||||
|
{e.line}: {e.message}
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
@ -289,10 +306,12 @@ const DynamicServiceEditor: React.FC = () => {
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{publishResult && !publishResult.success && (
|
{publishResult && !publishResult.success && (
|
||||||
<div className="flex items-start gap-3 rounded-lg border bg-red-50 border-red-200 text-red-800 px-4 py-3 text-sm">
|
<div className="flex items-start gap-3 rounded-lg border bg-red-50 dark:bg-red-900/20 border-red-200 dark:border-red-700 text-red-800 dark:text-red-300 px-4 py-3 text-sm">
|
||||||
<FaExclamationCircle className="w-4 h-4 mt-0.5 shrink-0 text-red-600" />
|
<FaExclamationCircle className="w-4 h-4 mt-0.5 shrink-0 text-red-600" />
|
||||||
<div>
|
<div>
|
||||||
<span className="font-medium">{translate('::App.DeveloperKit.DynamicServices.Editor.PublishFailed')}</span>
|
<span className="font-medium">
|
||||||
|
{translate('::App.DeveloperKit.DynamicServices.Editor.PublishFailed')}
|
||||||
|
</span>
|
||||||
{publishResult.errorMessage && (
|
{publishResult.errorMessage && (
|
||||||
<p className="text-xs mt-0.5">{publishResult.errorMessage}</p>
|
<p className="text-xs mt-0.5">{publishResult.errorMessage}</p>
|
||||||
)}
|
)}
|
||||||
|
|
@ -303,16 +322,20 @@ const DynamicServiceEditor: React.FC = () => {
|
||||||
{/* Two-panel layout */}
|
{/* Two-panel layout */}
|
||||||
<div className="flex flex-col xl:flex-row gap-4 items-stretch xl:items-start">
|
<div className="flex flex-col xl:flex-row gap-4 items-stretch xl:items-start">
|
||||||
{/* LEFT PANEL — Servis Ayarları */}
|
{/* LEFT PANEL — Servis Ayarları */}
|
||||||
<div className="w-full xl:w-1/4 shrink-0 bg-white rounded-lg border border-slate-200 p-5 space-y-4">
|
<div className="w-full xl:w-1/4 shrink-0 bg-white dark:bg-gray-900 rounded-lg border border-slate-200 dark:border-gray-700 p-5 space-y-4">
|
||||||
{/* Panel header */}
|
{/* Panel header */}
|
||||||
<div className="flex items-center gap-2 pb-3 border-b border-slate-100">
|
<div className="flex items-center gap-2 pb-3 border-b border-slate-100 dark:border-gray-700">
|
||||||
<FaCog className="w-4 h-4 text-blue-500" />
|
<FaCog className="w-4 h-4 text-blue-500" />
|
||||||
<h2 className="font-semibold text-slate-700 text-sm">{translate('::App.DeveloperKit.DynamicServices.Editor.ServiceSettings')}</h2>
|
<h2 className="font-semibold text-slate-700 dark:text-gray-100 text-sm">
|
||||||
|
{translate('::App.DeveloperKit.DynamicServices.Editor.ServiceSettings')}
|
||||||
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Servis Adı */}
|
{/* Servis Adı */}
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-slate-700 mb-1">{translate('::App.DeveloperKit.DynamicServices.Editor.ServiceName')}</label>
|
<label className="block text-sm font-medium text-slate-700 dark:text-gray-300 mb-1">
|
||||||
|
{translate('::App.DeveloperKit.DynamicServices.Editor.ServiceName')}
|
||||||
|
</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={serviceName}
|
value={serviceName}
|
||||||
|
|
@ -320,53 +343,76 @@ const DynamicServiceEditor: React.FC = () => {
|
||||||
setServiceName(e.target.value)
|
setServiceName(e.target.value)
|
||||||
setSubmitted(false)
|
setSubmitted(false)
|
||||||
}}
|
}}
|
||||||
placeholder={translate('::App.DeveloperKit.DynamicServices.Editor.ServiceNamePlaceholder')}
|
placeholder={translate(
|
||||||
className={`w-full px-3 py-2 border rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-colors ${
|
'::App.DeveloperKit.DynamicServices.Editor.ServiceNamePlaceholder',
|
||||||
serviceNameError ? 'border-red-500 bg-red-50' : 'border-slate-300'
|
)}
|
||||||
|
className={`w-full px-3 py-2 border rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-colors bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 ${
|
||||||
|
serviceNameError
|
||||||
|
? 'border-red-500 bg-red-50 dark:bg-red-900/20'
|
||||||
|
: 'border-slate-300 dark:border-gray-700'
|
||||||
}`}
|
}`}
|
||||||
|
autoFocus
|
||||||
/>
|
/>
|
||||||
{serviceNameError && <p className="text-red-500 text-xs mt-1">{translate('::App.DeveloperKit.DynamicServices.Editor.ServiceNameRequired')}</p>}
|
{serviceNameError && (
|
||||||
|
<p className="text-red-500 dark:text-red-400 text-xs mt-1">
|
||||||
|
{translate('::App.DeveloperKit.DynamicServices.Editor.ServiceNameRequired')}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Görünen Ad */}
|
{/* Görünen Ad */}
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-slate-700 mb-1">{translate('::App.DeveloperKit.DynamicServices.Editor.DisplayName')}</label>
|
<label className="block text-sm font-medium text-slate-700 dark:text-gray-300 mb-1">
|
||||||
|
{translate('::App.DeveloperKit.DynamicServices.Editor.DisplayName')}
|
||||||
|
</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={displayName}
|
value={displayName}
|
||||||
onChange={(e) => setDisplayName(e.target.value)}
|
onChange={(e) => setDisplayName(e.target.value)}
|
||||||
placeholder={translate('::App.DeveloperKit.DynamicServices.Editor.DisplayNamePlaceholder')}
|
placeholder={translate(
|
||||||
className="w-full px-3 py-2 border border-slate-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
'::App.DeveloperKit.DynamicServices.Editor.DisplayNamePlaceholder',
|
||||||
|
)}
|
||||||
|
className="w-full px-3 py-2 border border-slate-300 dark:border-gray-700 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Açıklama */}
|
{/* Açıklama */}
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-slate-700 mb-1">{translate('::App.DeveloperKit.DynamicServices.Editor.Description')}</label>
|
<label className="block text-sm font-medium text-slate-700 dark:text-gray-300 mb-1">
|
||||||
|
{translate('::App.DeveloperKit.DynamicServices.Editor.Description')}
|
||||||
|
</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={description}
|
value={description}
|
||||||
onChange={(e) => setDescription(e.target.value)}
|
onChange={(e) => setDescription(e.target.value)}
|
||||||
placeholder={translate('::App.DeveloperKit.DynamicServices.Editor.DescriptionPlaceholder')}
|
placeholder={translate(
|
||||||
className="w-full px-3 py-2 border border-slate-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
'::App.DeveloperKit.DynamicServices.Editor.DescriptionPlaceholder',
|
||||||
|
)}
|
||||||
|
className="w-full px-3 py-2 border border-slate-300 dark:border-gray-700 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Ana Entity Türü */}
|
{/* Ana Entity Türü */}
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-slate-700 mb-1">{translate('::App.DeveloperKit.DynamicServices.Editor.PrimaryEntityType')}</label>
|
<label className="block text-sm font-medium text-slate-700 dark:text-gray-300 mb-1">
|
||||||
|
{translate('::App.DeveloperKit.DynamicServices.Editor.PrimaryEntityType')}
|
||||||
|
</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={primaryEntityType}
|
value={primaryEntityType}
|
||||||
onChange={(e) => setPrimaryEntityType(e.target.value)}
|
onChange={(e) => setPrimaryEntityType(e.target.value)}
|
||||||
placeholder={translate('::App.DeveloperKit.DynamicServices.Editor.PrimaryEntityTypePlaceholder')}
|
placeholder={translate(
|
||||||
className="w-full px-3 py-2 border border-slate-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
'::App.DeveloperKit.DynamicServices.Editor.PrimaryEntityTypePlaceholder',
|
||||||
|
)}
|
||||||
|
className="w-full px-3 py-2 border border-slate-300 dark:border-gray-700 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Aktif */}
|
{/* Aktif */}
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-slate-700 mb-1">{translate('::App.DeveloperKit.DynamicServices.Editor.IsActive')}</label>
|
<label className="block text-sm font-medium text-slate-700 mb-1">
|
||||||
|
{translate('::App.DeveloperKit.DynamicServices.Editor.IsActive')}
|
||||||
|
</label>
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={isActive}
|
checked={isActive}
|
||||||
|
|
@ -380,15 +426,22 @@ const DynamicServiceEditor: React.FC = () => {
|
||||||
<div className="w-full flex-1 min-w-0 space-y-4">
|
<div className="w-full flex-1 min-w-0 space-y-4">
|
||||||
{/* Monaco Editor */}
|
{/* Monaco Editor */}
|
||||||
<div className="bg-white rounded-lg border border-slate-200 overflow-hidden">
|
<div className="bg-white rounded-lg border border-slate-200 overflow-hidden">
|
||||||
<div className="px-5 py-3 bg-slate-50 border-b border-slate-200 flex items-center justify-between">
|
<div className="px-5 py-3 bg-slate-50 dark:bg-gray-800 border-b border-slate-200 dark:border-gray-700 flex items-center justify-between">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<FaCode className="w-4 h-4 text-slate-500" />
|
<FaCode className="w-4 h-4 text-slate-500 dark:text-gray-400" />
|
||||||
<h3 className="font-medium text-slate-700 text-sm">{translate('::App.DeveloperKit.DynamicServices.Editor.CodeEditor')}</h3>
|
<h3 className="font-medium text-slate-700 dark:text-gray-100 text-sm">
|
||||||
|
{translate('::App.DeveloperKit.DynamicServices.Editor.CodeEditor')}
|
||||||
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2 text-xs text-slate-500">
|
<div className="flex items-center gap-2 text-xs text-slate-500 dark:text-gray-400">
|
||||||
<span>{translate('::App.DeveloperKit.DynamicServices.Editor.LineCount')} {code.split('\n').length}</span>
|
<span>
|
||||||
|
{translate('::App.DeveloperKit.DynamicServices.Editor.LineCount')}{' '}
|
||||||
|
{code.split('\n').length}
|
||||||
|
</span>
|
||||||
<span className="text-slate-300">|</span>
|
<span className="text-slate-300">|</span>
|
||||||
<span>{translate('::App.DeveloperKit.DynamicServices.Editor.CharCount')} {code.length}</span>
|
<span>
|
||||||
|
{translate('::App.DeveloperKit.DynamicServices.Editor.CharCount')} {code.length}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ height: '560px' }}>
|
<div style={{ height: '560px' }}>
|
||||||
|
|
|
||||||
|
|
@ -137,17 +137,17 @@ const DynamicServiceManager: React.FC = () => {
|
||||||
placeholder={translate('::App.DeveloperKit.DynamicServices.SearchPlaceholder')}
|
placeholder={translate('::App.DeveloperKit.DynamicServices.SearchPlaceholder')}
|
||||||
value={searchTerm}
|
value={searchTerm}
|
||||||
onChange={(e) => setSearchTerm(e.target.value)}
|
onChange={(e) => setSearchTerm(e.target.value)}
|
||||||
className="w-full pl-10 pr-4 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
className="w-full pl-10 pr-4 py-2 border border-slate-300 dark:border-gray-700 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2 w-full lg:w-auto">
|
<div className="flex items-center gap-2 w-full lg:w-auto">
|
||||||
<FaFilter className="w-4 h-4 text-slate-500" />
|
<FaFilter className="w-4 h-4 text-slate-500 dark:text-gray-400" />
|
||||||
<select
|
<select
|
||||||
value={filterStatus}
|
value={filterStatus}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
setFilterStatus(e.target.value as 'all' | 'Success' | 'Failed' | 'Pending')
|
setFilterStatus(e.target.value as 'all' | 'Success' | 'Failed' | 'Pending')
|
||||||
}
|
}
|
||||||
className="w-full lg:w-auto px-3 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
className="w-full lg:w-auto px-3 py-2 border border-slate-300 dark:border-gray-700 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100"
|
||||||
>
|
>
|
||||||
<option value="all">{translate('::App.DeveloperKit.DynamicServices.FilterAll')}</option>
|
<option value="all">{translate('::App.DeveloperKit.DynamicServices.FilterAll')}</option>
|
||||||
<option value="Success">{translate('::App.DeveloperKit.DynamicServices.Successful')}</option>
|
<option value="Success">{translate('::App.DeveloperKit.DynamicServices.Successful')}</option>
|
||||||
|
|
@ -158,7 +158,7 @@ const DynamicServiceManager: React.FC = () => {
|
||||||
<div className="w-full sm:w-auto">
|
<div className="w-full sm:w-auto">
|
||||||
<Link
|
<Link
|
||||||
to={ROUTES_ENUM.protected.saas.developerKit.dynamicServicesNew}
|
to={ROUTES_ENUM.protected.saas.developerKit.dynamicServicesNew}
|
||||||
className="w-full sm:w-auto justify-center flex items-center gap-2 bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700 transition-colors"
|
className="w-full sm:w-auto justify-center flex items-center gap-2 bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700 dark:bg-blue-700 dark:hover:bg-blue-800 transition-colors"
|
||||||
>
|
>
|
||||||
<FaPlus className="w-4 h-4" />
|
<FaPlus className="w-4 h-4" />
|
||||||
{translate('::App.DeveloperKit.DynamicServices.NewService')}
|
{translate('::App.DeveloperKit.DynamicServices.NewService')}
|
||||||
|
|
@ -167,7 +167,7 @@ const DynamicServiceManager: React.FC = () => {
|
||||||
<div className="w-full sm:w-auto">
|
<div className="w-full sm:w-auto">
|
||||||
<button
|
<button
|
||||||
onClick={openSwagger}
|
onClick={openSwagger}
|
||||||
className="w-full sm:w-auto justify-center flex items-center gap-2 bg-green-600 text-white px-4 py-2 rounded-lg hover:bg-green-700 transition-colors"
|
className="w-full sm:w-auto justify-center flex items-center gap-2 bg-green-600 text-white px-4 py-2 rounded-lg hover:bg-green-700 dark:bg-green-700 dark:hover:bg-green-800 transition-colors"
|
||||||
>
|
>
|
||||||
<FaExternalLinkAlt className="w-3 h-3" />
|
<FaExternalLinkAlt className="w-3 h-3" />
|
||||||
Swagger
|
Swagger
|
||||||
|
|
@ -185,35 +185,35 @@ const DynamicServiceManager: React.FC = () => {
|
||||||
{filteredServices.map((service) => (
|
{filteredServices.map((service) => (
|
||||||
<div
|
<div
|
||||||
key={service.id}
|
key={service.id}
|
||||||
className="bg-white rounded-lg border border-slate-200 shadow-sm hover:shadow-md transition-shadow"
|
className="bg-white dark:bg-gray-900 rounded-lg border border-slate-200 dark:border-gray-700 shadow-sm hover:shadow-md transition-shadow"
|
||||||
>
|
>
|
||||||
<div className="p-6">
|
<div className="p-6">
|
||||||
<div className="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
|
<div className="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<div className="flex items-center gap-2 mb-1">
|
<div className="flex items-center gap-2 mb-1">
|
||||||
<h3 className="text-base font-semibold text-slate-900">{service.name}</h3>
|
<h3 className="text-base font-semibold text-slate-900 dark:text-gray-100">{service.name}</h3>
|
||||||
<div
|
<div
|
||||||
className={`w-2 h-2 rounded-full ${
|
className={`w-2 h-2 rounded-full ${
|
||||||
service.compilationStatus === 'Success'
|
service.compilationStatus === 'Success'
|
||||||
? 'bg-emerald-500'
|
? 'bg-emerald-500'
|
||||||
: 'bg-slate-300'
|
: 'bg-slate-300 dark:bg-gray-700'
|
||||||
}`}
|
}`}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{service.displayName && (
|
{service.displayName && (
|
||||||
<p className="text-slate-500 text-sm mb-1">{service.displayName}</p>
|
<p className="text-slate-500 dark:text-gray-400 text-sm mb-1">{service.displayName}</p>
|
||||||
)}
|
)}
|
||||||
<span
|
<span
|
||||||
className={`inline-block px-2 py-0.5 text-xs rounded-full font-medium mb-3 ${statusBadge(service.compilationStatus)}`}
|
className={`inline-block px-2 py-0.5 text-xs rounded-full font-medium mb-3 ${statusBadge(service.compilationStatus)} dark:bg-opacity-80`}
|
||||||
>
|
>
|
||||||
{service.compilationStatus} · v{service.version}
|
{service.compilationStatus} · v{service.version}
|
||||||
</span>
|
</span>
|
||||||
{service.description && (
|
{service.description && (
|
||||||
<p className="text-slate-500 text-sm">{service.description}</p>
|
<p className="text-slate-500 dark:text-gray-400 text-sm">{service.description}</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{service.lastSuccessfulCompilation && (
|
{service.lastSuccessfulCompilation && (
|
||||||
<div className="flex items-center gap-1 text-xs text-slate-400 sm:ml-4 whitespace-nowrap">
|
<div className="flex items-center gap-1 text-xs text-slate-400 dark:text-gray-400 sm:ml-4 whitespace-nowrap">
|
||||||
<FaCalendarAlt className="w-3 h-3" />
|
<FaCalendarAlt className="w-3 h-3" />
|
||||||
<span>
|
<span>
|
||||||
{new Date(service.lastSuccessfulCompilation).toLocaleDateString()}
|
{new Date(service.lastSuccessfulCompilation).toLocaleDateString()}
|
||||||
|
|
@ -223,20 +223,20 @@ const DynamicServiceManager: React.FC = () => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Actions */}
|
{/* Actions */}
|
||||||
<div className="flex items-center justify-end pt-3 border-t border-slate-100 gap-1 mt-4">
|
<div className="flex items-center justify-end pt-3 border-t border-slate-100 dark:border-gray-700 gap-1 mt-4">
|
||||||
<Link
|
<Link
|
||||||
to={ROUTES_ENUM.protected.saas.developerKit.dynamicServicesEdit.replace(
|
to={ROUTES_ENUM.protected.saas.developerKit.dynamicServicesEdit.replace(
|
||||||
':id',
|
':id',
|
||||||
service.id,
|
service.id,
|
||||||
)}
|
)}
|
||||||
className="p-2 text-slate-500 hover:text-blue-600 hover:bg-blue-50 rounded transition-colors"
|
className="p-2 text-slate-500 dark:text-gray-400 hover:text-blue-600 dark:hover:text-blue-400 hover:bg-blue-50 dark:hover:bg-gray-800 rounded transition-colors"
|
||||||
title={translate('::App.DeveloperKit.DynamicServices.EditTooltip')}
|
title={translate('::App.DeveloperKit.DynamicServices.EditTooltip')}
|
||||||
>
|
>
|
||||||
<FaRegEdit className="w-4 h-4" />
|
<FaRegEdit className="w-4 h-4" />
|
||||||
</Link>
|
</Link>
|
||||||
<button
|
<button
|
||||||
onClick={() => deleteService(service.id)}
|
onClick={() => deleteService(service.id)}
|
||||||
className="p-2 text-slate-500 hover:text-red-600 hover:bg-red-50 rounded transition-colors"
|
className="p-2 text-slate-500 dark:text-gray-400 hover:text-red-600 dark:hover:text-red-400 hover:bg-red-50 dark:hover:bg-gray-800 rounded transition-colors"
|
||||||
title={translate('::App.DeveloperKit.DynamicServices.DeleteTooltip')}
|
title={translate('::App.DeveloperKit.DynamicServices.DeleteTooltip')}
|
||||||
>
|
>
|
||||||
<FaTrashAlt className="w-4 h-4" />
|
<FaTrashAlt className="w-4 h-4" />
|
||||||
|
|
@ -248,13 +248,13 @@ const DynamicServiceManager: React.FC = () => {
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="text-center py-12">
|
<div className="text-center py-12">
|
||||||
<div className="bg-slate-100 rounded-full w-16 h-16 flex items-center justify-center mx-auto mb-4">
|
<div className="bg-slate-100 dark:bg-gray-800 rounded-full w-16 h-16 flex items-center justify-center mx-auto mb-4">
|
||||||
<FaCode className="w-8 h-8 text-slate-400" />
|
<FaCode className="w-8 h-8 text-slate-400 dark:text-gray-500" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="text-lg font-medium text-slate-900 mb-2">
|
<h3 className="text-lg font-medium text-slate-900 dark:text-gray-100 mb-2">
|
||||||
{searchTerm || filterStatus !== 'all' ? translate('::App.DeveloperKit.DynamicServices.NoResults') : translate('::App.DeveloperKit.DynamicServices.NoServicesYet')}
|
{searchTerm || filterStatus !== 'all' ? translate('::App.DeveloperKit.DynamicServices.NoResults') : translate('::App.DeveloperKit.DynamicServices.NoServicesYet')}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-slate-500 mb-6">
|
<p className="text-slate-500 dark:text-gray-400 mb-6">
|
||||||
{searchTerm || filterStatus !== 'all'
|
{searchTerm || filterStatus !== 'all'
|
||||||
? translate('::App.DeveloperKit.DynamicServices.TryChangingFilter')
|
? translate('::App.DeveloperKit.DynamicServices.TryChangingFilter')
|
||||||
: translate('::App.DeveloperKit.DynamicServices.GetStarted')}
|
: translate('::App.DeveloperKit.DynamicServices.GetStarted')}
|
||||||
|
|
@ -262,7 +262,7 @@ const DynamicServiceManager: React.FC = () => {
|
||||||
{!searchTerm && filterStatus === 'all' && (
|
{!searchTerm && filterStatus === 'all' && (
|
||||||
<Link
|
<Link
|
||||||
to={ROUTES_ENUM.protected.saas.developerKit.dynamicServicesNew}
|
to={ROUTES_ENUM.protected.saas.developerKit.dynamicServicesNew}
|
||||||
className="inline-flex items-center gap-2 bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700 transition-colors"
|
className="inline-flex items-center gap-2 bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700 dark:bg-blue-700 dark:hover:bg-blue-800 transition-colors"
|
||||||
>
|
>
|
||||||
<FaPlus className="w-4 h-4" />
|
<FaPlus className="w-4 h-4" />
|
||||||
{translate('::App.DeveloperKit.DynamicServices.CreateNewService')}
|
{translate('::App.DeveloperKit.DynamicServices.CreateNewService')}
|
||||||
|
|
|
||||||
|
|
@ -392,12 +392,12 @@ const SqlObjectExplorer = ({
|
||||||
placeholder={translate('::App.Platform.Search')}
|
placeholder={translate('::App.Platform.Search')}
|
||||||
value={filterText}
|
value={filterText}
|
||||||
onChange={(e) => setFilterText(e.target.value)}
|
onChange={(e) => setFilterText(e.target.value)}
|
||||||
className="flex-1 px-3 py-1.5 text-sm border rounded-md bg-white dark:bg-gray-700 dark:border-gray-600"
|
className="flex-1 px-3 py-1.5 text-sm border rounded-md bg-white dark:bg-gray-800 border-gray-300 dark:border-gray-700 text-gray-900 dark:text-gray-100"
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
onClick={loadObjects}
|
onClick={loadObjects}
|
||||||
disabled={loading || !dataSource}
|
disabled={loading || !dataSource}
|
||||||
className="px-3 py-1.5 text-sm border rounded-md hover:bg-gray-100 dark:hover:bg-gray-700 disabled:opacity-50"
|
className="px-3 py-1.5 text-sm border rounded-md border-gray-300 dark:border-gray-700 hover:bg-gray-100 dark:hover:bg-gray-700 disabled:opacity-50 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100"
|
||||||
title={translate('::App.Platform.Refresh')}
|
title={translate('::App.Platform.Refresh')}
|
||||||
>
|
>
|
||||||
<FaSyncAlt className={loading ? 'animate-spin' : ''} />
|
<FaSyncAlt className={loading ? 'animate-spin' : ''} />
|
||||||
|
|
|
||||||
|
|
@ -893,7 +893,7 @@ GO`,
|
||||||
<div className="flex flex-wrap items-center gap-2 sm:gap-3">
|
<div className="flex flex-wrap items-center gap-2 sm:gap-3">
|
||||||
<FaDatabase className="text-lg text-blue-500" />
|
<FaDatabase className="text-lg text-blue-500" />
|
||||||
<select
|
<select
|
||||||
className="border border-gray-300 rounded px-2 py-1 max-w-full"
|
className="border border-gray-300 rounded px-2 py-1 max-w-full dark:bg-gray-700 dark:border-gray-600"
|
||||||
disabled={state.selectedDataSource?.length === 0}
|
disabled={state.selectedDataSource?.length === 0}
|
||||||
value={state.selectedDataSource || ''}
|
value={state.selectedDataSource || ''}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ const SqlResultsGrid = ({ result }: SqlResultsGridProps) => {
|
||||||
return (
|
return (
|
||||||
<div className="h-full flex flex-col">
|
<div className="h-full flex flex-col">
|
||||||
<div className="flex items-center gap-2 mb-4 p-4 bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded">
|
<div className="flex items-center gap-2 mb-4 p-4 bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded">
|
||||||
<FaTimesCircle className="text-red-500 text-xl" />
|
<FaTimesCircle className="text-red-500 dark:text-red-400 text-xl" />
|
||||||
<div>
|
<div>
|
||||||
<div className="font-semibold text-red-700 dark:text-red-400">
|
<div className="font-semibold text-red-700 dark:text-red-400">
|
||||||
{translate('::App.Platform.Error')}
|
{translate('::App.Platform.Error')}
|
||||||
|
|
|
||||||
|
|
@ -878,7 +878,7 @@ function SimpleMenuTreeSelect({
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`rounded-lg border ${
|
className={`rounded-lg border ${
|
||||||
invalid ? 'border-red-500' : 'border-gray-300 dark:border-gray-600'
|
invalid ? 'border-red-500 dark:border-red-700' : 'border-gray-300 dark:border-gray-600'
|
||||||
} bg-white dark:bg-gray-800 overflow-hidden`}
|
} bg-white dark:bg-gray-800 overflow-hidden`}
|
||||||
>
|
>
|
||||||
<div className="h-56 overflow-y-auto py-1">
|
<div className="h-56 overflow-y-auto py-1">
|
||||||
|
|
|
||||||
|
|
@ -61,10 +61,10 @@ export function Forum() {
|
||||||
|
|
||||||
{error && (
|
{error && (
|
||||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4">
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4">
|
||||||
<div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative">
|
<div className="bg-red-100 dark:bg-red-900 border border-red-400 dark:border-red-700 text-red-700 dark:text-red-200 px-4 py-3 rounded relative">
|
||||||
<strong className="font-bold">Error: </strong>
|
<strong className="font-bold">Error: </strong>
|
||||||
<span className="block sm:inline">{error}</span>
|
<span className="block sm:inline">{error}</span>
|
||||||
<Button onClick={clearError} className="absolute top-0 bottom-0 right-0 px-4 py-3">
|
<Button onClick={clearError} className="absolute top-0 bottom-0 right-0 px-4 py-3 text-red-700 dark:text-red-200">
|
||||||
<span className="sr-only">Dismiss</span>×
|
<span className="sr-only">Dismiss</span>×
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -67,10 +67,10 @@ export function Management() {
|
||||||
|
|
||||||
{error && (
|
{error && (
|
||||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4">
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4">
|
||||||
<div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative">
|
<div className="bg-red-100 dark:bg-red-900 border border-red-400 dark:border-red-700 text-red-700 dark:text-red-200 px-4 py-3 rounded relative">
|
||||||
<strong className="font-bold">Error: </strong>
|
<strong className="font-bold">Error: </strong>
|
||||||
<span className="block sm:inline">{error}</span>
|
<span className="block sm:inline">{error}</span>
|
||||||
<Button onClick={clearError} className="absolute top-0 bottom-0 right-0 px-4 py-3">
|
<Button onClick={clearError} className="absolute top-0 bottom-0 right-0 px-4 py-3 text-red-700 dark:text-red-200">
|
||||||
<span className="sr-only">Dismiss</span>×
|
<span className="sr-only">Dismiss</span>×
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -104,7 +104,7 @@ export function AdminView({
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col lg:flex-row gap-4 mt-3">
|
<div className="flex flex-col lg:flex-row gap-4 mt-3">
|
||||||
{/* Sidebar Navigation */}
|
{/* Sidebar Navigation */}
|
||||||
<div className="lg:w-64 flex-shrink-0 p-4 bg-gray-50">
|
<div className="lg:w-64 flex-shrink-0 p-4 bg-gray-50 dark:bg-gray-900 rounded-xl shadow-sm border border-gray-200 dark:border-gray-700">
|
||||||
<nav className="space-y-2">
|
<nav className="space-y-2">
|
||||||
{navigationItems.map((item) => {
|
{navigationItems.map((item) => {
|
||||||
const Icon = item.icon
|
const Icon = item.icon
|
||||||
|
|
@ -116,9 +116,13 @@ export function AdminView({
|
||||||
color="blue-500"
|
color="blue-500"
|
||||||
active={isActive}
|
active={isActive}
|
||||||
onClick={() => setActiveSection(item.id)}
|
onClick={() => setActiveSection(item.id)}
|
||||||
className="w-full flex items-center space-x-3 px-4 py-3 text-left transition-colors"
|
className={`w-full flex items-center space-x-3 px-4 py-3 text-left transition-colors
|
||||||
|
${isActive
|
||||||
|
? 'bg-blue-100 dark:bg-blue-900 text-blue-700 dark:text-blue-300'
|
||||||
|
: 'bg-transparent text-gray-900 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-800'}
|
||||||
|
`}
|
||||||
>
|
>
|
||||||
<Icon className="w-5 h-5" />
|
<Icon className={`w-5 h-5 ${isActive ? 'text-blue-600 dark:text-blue-400' : 'text-gray-500 dark:text-gray-400'}`} />
|
||||||
<span className="font-medium">{item.label}</span>
|
<span className="font-medium">{item.label}</span>
|
||||||
</Button>
|
</Button>
|
||||||
)
|
)
|
||||||
|
|
@ -127,7 +131,7 @@ export function AdminView({
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Main Content */}
|
{/* Main Content */}
|
||||||
<div className="flex-1">
|
<div className="flex-1 bg-white dark:bg-gray-900 rounded-xl shadow-sm border border-gray-200 dark:border-gray-700 p-4">
|
||||||
{activeSection === 'stats' && (
|
{activeSection === 'stats' && (
|
||||||
<AdminStats categories={categories} topics={topics} posts={posts} />
|
<AdminStats categories={categories} topics={topics} posts={posts} />
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -163,7 +163,7 @@ export function CategoryManagement({
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
{/* Create/Edit Form */}
|
{/* Create/Edit Form */}
|
||||||
{showCreateForm && (
|
{showCreateForm && (
|
||||||
<div className="bg-white rounded-xl shadow-sm border border-gray-200 p-6">
|
<div className="bg-white dark:bg-gray-900 rounded-xl shadow-sm border border-gray-200 dark:border-gray-700 p-6">
|
||||||
<h3 className="text-lg font-semibold text-gray-900 mb-4">
|
<h3 className="text-lg font-semibold text-gray-900 mb-4">
|
||||||
{editingCategory
|
{editingCategory
|
||||||
? translate('::App.Forum.CategoryManagement.EditCategory')
|
? translate('::App.Forum.CategoryManagement.EditCategory')
|
||||||
|
|
@ -212,7 +212,7 @@ export function CategoryManagement({
|
||||||
>
|
>
|
||||||
<Field
|
<Field
|
||||||
name="name"
|
name="name"
|
||||||
className="w-full border border-gray-300 rounded-lg px-3 py-2"
|
className="w-full border border-gray-300 dark:border-gray-700 rounded-lg px-3 py-2 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 placeholder-gray-400 dark:placeholder-gray-500"
|
||||||
/>
|
/>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
|
|
||||||
|
|
@ -224,7 +224,7 @@ export function CategoryManagement({
|
||||||
>
|
>
|
||||||
<Field
|
<Field
|
||||||
name="slug"
|
name="slug"
|
||||||
className="w-full border border-gray-300 rounded-lg px-3 py-2"
|
className="w-full border border-gray-300 dark:border-gray-700 rounded-lg px-3 py-2 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 placeholder-gray-400 dark:placeholder-gray-500"
|
||||||
/>
|
/>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -237,7 +237,7 @@ export function CategoryManagement({
|
||||||
>
|
>
|
||||||
<Field
|
<Field
|
||||||
name="description"
|
name="description"
|
||||||
className="w-full border border-gray-300 rounded-lg px-3 py-2"
|
className="w-full border border-gray-300 dark:border-gray-700 rounded-lg px-3 py-2 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 placeholder-gray-400 dark:placeholder-gray-500"
|
||||||
textArea="true"
|
textArea="true"
|
||||||
component={Input}
|
component={Input}
|
||||||
/>
|
/>
|
||||||
|
|
@ -252,7 +252,7 @@ export function CategoryManagement({
|
||||||
>
|
>
|
||||||
<Field
|
<Field
|
||||||
name="icon"
|
name="icon"
|
||||||
className="w-full border border-gray-300 rounded-lg px-3 py-2"
|
className="w-full border border-gray-300 dark:border-gray-700 rounded-lg px-3 py-2 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 placeholder-gray-400 dark:placeholder-gray-500"
|
||||||
placeholder="💬"
|
placeholder="💬"
|
||||||
/>
|
/>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
|
|
@ -265,7 +265,7 @@ export function CategoryManagement({
|
||||||
>
|
>
|
||||||
<Field
|
<Field
|
||||||
name="displayOrder"
|
name="displayOrder"
|
||||||
className="w-full border border-gray-300 rounded-lg px-3 py-2"
|
className="w-full border border-gray-300 dark:border-gray-700 rounded-lg px-3 py-2 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 placeholder-gray-400 dark:placeholder-gray-500"
|
||||||
placeholder="💬"
|
placeholder="💬"
|
||||||
type="number"
|
type="number"
|
||||||
/>
|
/>
|
||||||
|
|
@ -306,9 +306,9 @@ export function CategoryManagement({
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Categories List */}
|
{/* Categories List */}
|
||||||
<div className="bg-white rounded-xl shadow-sm border border-gray-200 overflow-hidden">
|
<div className="bg-white dark:bg-gray-900 rounded-xl shadow-sm border border-gray-200 dark:border-gray-700 overflow-hidden">
|
||||||
<div className="flex items-center justify-between px-3 py-4 border-b border-gray-200">
|
<div className="flex items-center justify-between px-3 py-4 border-b border-gray-200 dark:border-gray-700">
|
||||||
<h3 className="text-lg font-semibold text-gray-900">
|
<h3 className="text-lg font-semibold text-gray-900 dark:text-gray-100">
|
||||||
{translate('::App.Forum.CategoryManagement.Categories')} ({categories.length})
|
{translate('::App.Forum.CategoryManagement.Categories')} ({categories.length})
|
||||||
</h3>
|
</h3>
|
||||||
<Button
|
<Button
|
||||||
|
|
@ -316,7 +316,7 @@ export function CategoryManagement({
|
||||||
variant="solid"
|
variant="solid"
|
||||||
onClick={() => setShowCreateForm(true)}
|
onClick={() => setShowCreateForm(true)}
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
className="flex items-center space-x-2 bg-blue-600 px-4 py-2 rounded-lg hover:bg-blue-700 transition-colors disabled:opacity-50"
|
className="flex items-center space-x-2 bg-blue-600 dark:bg-blue-700 px-4 py-2 rounded-lg hover:bg-blue-700 dark:hover:bg-blue-800 transition-colors disabled:opacity-50"
|
||||||
>
|
>
|
||||||
<FaPlus className="w-4 h-4" />
|
<FaPlus className="w-4 h-4" />
|
||||||
<span>{translate('::App.Forum.CategoryManagement.AddCategory')}</span>
|
<span>{translate('::App.Forum.CategoryManagement.AddCategory')}</span>
|
||||||
|
|
@ -325,36 +325,36 @@ export function CategoryManagement({
|
||||||
|
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<div className="p-8 text-center">
|
<div className="p-8 text-center">
|
||||||
<FaSpinner className="w-8 h-8 animate-spin mx-auto mb-4 text-blue-600" />
|
<FaSpinner className="w-8 h-8 animate-spin mx-auto mb-4 text-blue-600 dark:text-blue-400" />
|
||||||
<p className="text-gray-500">
|
<p className="text-gray-500 dark:text-gray-400">
|
||||||
{translate('::App.Forum.CategoryManagement.Loadingcategories')}
|
{translate('::App.Forum.CategoryManagement.Loadingcategories')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="divide-y divide-gray-200">
|
<div className="divide-y divide-gray-200 dark:divide-gray-800">
|
||||||
{categories
|
{categories
|
||||||
.sort((a, b) => a.displayOrder - b.displayOrder)
|
.sort((a, b) => a.displayOrder - b.displayOrder)
|
||||||
.map((category) => (
|
.map((category) => (
|
||||||
<div key={category.id} className="p-6 hover:bg-gray-50 transition-colors">
|
<div key={category.id} className="p-6 hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex items-start space-x-4">
|
<div className="flex items-start space-x-4">
|
||||||
<div className="text-2xl">{category.icon}</div>
|
<div className="text-2xl">{category.icon}</div>
|
||||||
<div>
|
<div>
|
||||||
<div className="flex items-center space-x-2 mb-1">
|
<div className="flex items-center space-x-2 mb-1">
|
||||||
<h4 className="text-lg font-semibold text-gray-900">{category.name}</h4>
|
<h4 className="text-lg font-semibold text-gray-900 dark:text-gray-100">{category.name}</h4>
|
||||||
{!category.isActive && (
|
{!category.isActive && (
|
||||||
<span className="px-2 py-1 bg-red-100 text-red-700 text-xs rounded-full">
|
<span className="px-2 py-1 bg-red-100 dark:bg-red-900 text-red-700 dark:text-red-300 text-xs rounded-full">
|
||||||
Inactive
|
Inactive
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{category.isLocked && (
|
{category.isLocked && (
|
||||||
<span className="px-2 py-1 bg-yellow-100 text-yellow-700 text-xs rounded-full">
|
<span className="px-2 py-1 bg-yellow-100 dark:bg-yellow-900 text-yellow-700 dark:text-yellow-300 text-xs rounded-full">
|
||||||
Locked
|
Locked
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<p className="text-gray-600 mb-2">{category.description}</p>
|
<p className="text-gray-600 dark:text-gray-300 mb-2">{category.description}</p>
|
||||||
<div className="flex items-center space-x-4 text-sm text-gray-500">
|
<div className="flex items-center space-x-4 text-sm text-gray-500 dark:text-gray-400">
|
||||||
<span>{category.topicCount} topics</span>
|
<span>{category.topicCount} topics</span>
|
||||||
<span>{category.postCount} posts</span>
|
<span>{category.postCount} posts</span>
|
||||||
<span>Order: {category.displayOrder}</span>
|
<span>Order: {category.displayOrder}</span>
|
||||||
|
|
@ -368,8 +368,8 @@ export function CategoryManagement({
|
||||||
onClick={() => handleToggleActive(category)}
|
onClick={() => handleToggleActive(category)}
|
||||||
className={`p-1 rounded-lg transition-colors ${
|
className={`p-1 rounded-lg transition-colors ${
|
||||||
category.isActive
|
category.isActive
|
||||||
? 'text-green-600 hover:bg-green-100'
|
? 'text-green-600 dark:text-green-400 hover:bg-green-100 dark:hover:bg-green-900'
|
||||||
: 'text-red-600 hover:bg-red-100'
|
: 'text-red-600 dark:text-red-400 hover:bg-red-100 dark:hover:bg-red-900'
|
||||||
}`}
|
}`}
|
||||||
title={category.isActive ? 'Hide Category' : 'Show Category'}
|
title={category.isActive ? 'Hide Category' : 'Show Category'}
|
||||||
>
|
>
|
||||||
|
|
@ -385,8 +385,8 @@ export function CategoryManagement({
|
||||||
onClick={() => handleToggleLocked(category)}
|
onClick={() => handleToggleLocked(category)}
|
||||||
className={`p-1 rounded-lg transition-colors ${
|
className={`p-1 rounded-lg transition-colors ${
|
||||||
category.isLocked
|
category.isLocked
|
||||||
? 'text-yellow-600 hover:bg-yellow-100'
|
? 'text-yellow-600 dark:text-yellow-300 hover:bg-yellow-100 dark:hover:bg-yellow-900'
|
||||||
: 'text-green-600 hover:bg-green-100'
|
: 'text-green-600 dark:text-green-400 hover:bg-green-100 dark:hover:bg-green-900'
|
||||||
}`}
|
}`}
|
||||||
title={category.isLocked ? 'Unlock Category' : 'Lock Category'}
|
title={category.isLocked ? 'Unlock Category' : 'Lock Category'}
|
||||||
>
|
>
|
||||||
|
|
@ -400,7 +400,7 @@ export function CategoryManagement({
|
||||||
<Button
|
<Button
|
||||||
size="xs"
|
size="xs"
|
||||||
onClick={() => handleEdit(category)}
|
onClick={() => handleEdit(category)}
|
||||||
className="p-1 text-blue-600 hover:bg-blue-100 rounded-lg transition-colors"
|
className="p-1 text-blue-600 dark:text-blue-400 hover:bg-blue-100 dark:hover:bg-blue-900 rounded-lg transition-colors"
|
||||||
title={translate('::App.Forum.CategoryManagement.EditCategory')}
|
title={translate('::App.Forum.CategoryManagement.EditCategory')}
|
||||||
>
|
>
|
||||||
<FaEdit className="w-3 h-3" />
|
<FaEdit className="w-3 h-3" />
|
||||||
|
|
@ -409,7 +409,7 @@ export function CategoryManagement({
|
||||||
<Button
|
<Button
|
||||||
size="xs"
|
size="xs"
|
||||||
onClick={() => confirmDeleteCategory(category)}
|
onClick={() => confirmDeleteCategory(category)}
|
||||||
className="p-1 text-red-600 hover:bg-red-100 rounded-lg transition-colors"
|
className="p-1 text-red-600 dark:text-red-400 hover:bg-red-100 dark:hover:bg-red-900 rounded-lg transition-colors"
|
||||||
title={translate('::App.Forum.CategoryManagement.DeleteCategory')}
|
title={translate('::App.Forum.CategoryManagement.DeleteCategory')}
|
||||||
>
|
>
|
||||||
<FaTrash className="w-3 h-3" />
|
<FaTrash className="w-3 h-3" />
|
||||||
|
|
|
||||||
|
|
@ -116,17 +116,17 @@ export function AdminStats({ categories, topics, posts }: AdminStatsProps) {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Recent Activity */}
|
{/* Recent Activity */}
|
||||||
<div className="bg-white rounded-xl shadow-sm border border-gray-200 p-6">
|
<div className="bg-white dark:bg-gray-900 rounded-xl shadow-sm border border-gray-200 dark:border-gray-700 p-6">
|
||||||
<h3 className="text-lg font-semibold text-gray-900 mb-4">
|
<h3 className="text-lg font-semibold text-gray-900 dark:text-white mb-4">
|
||||||
{translate('::App.Forum.Dashboard.RecentActivity')}
|
{translate('::App.Forum.Dashboard.RecentActivity')}
|
||||||
</h3>
|
</h3>
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
{latestActivities.map((activity, index) => (
|
{latestActivities.map((activity, index) => (
|
||||||
<div key={index} className="flex items-start space-x-3 p-3 bg-gray-50 rounded-lg">
|
<div key={index} className="flex items-start space-x-3 p-3 bg-gray-50 dark:bg-gray-800 rounded-lg">
|
||||||
<div className={`w-2 h-2 ${activity.color} rounded-full mt-2`} />
|
<div className={`w-2 h-2 ${activity.color} rounded-full mt-2`} />
|
||||||
<div>
|
<div>
|
||||||
<p className="text-sm text-gray-900">{activity.message}</p>
|
<p className="text-sm text-gray-900 dark:text-white">{activity.message}</p>
|
||||||
<p className="text-xs text-gray-500">{dayjs(activity.date).fromNow()}</p>
|
<p className="text-xs text-gray-500 dark:text-gray-400">{dayjs(activity.date).fromNow()}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|
|
||||||
|
|
@ -162,7 +162,7 @@ export function PostManagement({
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
{/* Create/Edit Form */}
|
{/* Create/Edit Form */}
|
||||||
{showCreateForm && (
|
{showCreateForm && (
|
||||||
<div className="bg-white rounded-xl shadow-sm border border-gray-200 p-6">
|
<div className="bg-white dark:bg-gray-900 rounded-xl shadow-sm border border-gray-200 dark:border-gray-700 p-6">
|
||||||
<h3 className="text-lg font-semibold text-gray-900 mb-4">
|
<h3 className="text-lg font-semibold text-gray-900 mb-4">
|
||||||
{editingPost
|
{editingPost
|
||||||
? translate('::App.Forum.PostManagement.EditPost')
|
? translate('::App.Forum.PostManagement.EditPost')
|
||||||
|
|
@ -206,7 +206,7 @@ export function PostManagement({
|
||||||
<Field
|
<Field
|
||||||
as="select"
|
as="select"
|
||||||
name="topicId"
|
name="topicId"
|
||||||
className="w-full border border-gray-300 rounded-lg px-3 py-2"
|
className="w-full border border-gray-300 dark:border-gray-700 rounded-lg px-3 py-2 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100"
|
||||||
>
|
>
|
||||||
<option value="">Select a topic</option>
|
<option value="">Select a topic</option>
|
||||||
{topics.map((topic) => (
|
{topics.map((topic) => (
|
||||||
|
|
@ -330,15 +330,15 @@ export function PostManagement({
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Posts List */}
|
{/* Posts List */}
|
||||||
<div className="bg-white rounded-xl shadow-sm border border-gray-200 overflow-hidden">
|
<div className="bg-white dark:bg-gray-900 rounded-xl shadow-sm border border-gray-200 dark:border-gray-700 overflow-hidden">
|
||||||
<div className="flex items-center justify-between px-3 py-4 border-b border-gray-200">
|
<div className="flex items-center justify-between px-3 py-4 border-b border-gray-200 dark:border-gray-700">
|
||||||
<h3 className="text-lg font-semibold text-gray-900">Posts ({posts.length})</h3>
|
<h3 className="text-lg font-semibold text-gray-900 dark:text-gray-100">Posts ({posts.length})</h3>
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="solid"
|
variant="solid"
|
||||||
onClick={() => setShowCreateForm(true)}
|
onClick={() => setShowCreateForm(true)}
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
className="flex items-center space-x-2 bg-blue-600 px-4 py-2 rounded-lg hover:bg-blue-700 transition-colors disabled:opacity-50"
|
className="flex items-center space-x-2 bg-blue-600 dark:bg-blue-700 px-4 py-2 rounded-lg hover:bg-blue-700 dark:hover:bg-blue-800 transition-colors disabled:opacity-50"
|
||||||
>
|
>
|
||||||
<FaPlus className="w-4 h-4" />
|
<FaPlus className="w-4 h-4" />
|
||||||
<span>{translate('::App.Forum.PostManagement.AddPost')}</span>
|
<span>{translate('::App.Forum.PostManagement.AddPost')}</span>
|
||||||
|
|
@ -347,23 +347,23 @@ export function PostManagement({
|
||||||
|
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<div className="p-8 text-center">
|
<div className="p-8 text-center">
|
||||||
<FaSpinner className="w-8 h-8 animate-spin mx-auto mb-4 text-blue-600" />
|
<FaSpinner className="w-8 h-8 animate-spin mx-auto mb-4 text-blue-600 dark:text-blue-400" />
|
||||||
<p className="text-gray-500">{translate('::App.Forum.PostManagement.Loadingtopics')}</p>
|
<p className="text-gray-500 dark:text-gray-400">{translate('::App.Forum.PostManagement.Loadingtopics')}</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="divide-y divide-gray-200">
|
<div className="divide-y divide-gray-200 dark:divide-gray-800">
|
||||||
{posts
|
{posts
|
||||||
.sort(
|
.sort(
|
||||||
(a, b) => new Date(b.creationTime).getTime() - new Date(a.creationTime).getTime(),
|
(a, b) => new Date(b.creationTime).getTime() - new Date(a.creationTime).getTime(),
|
||||||
)
|
)
|
||||||
.map((post) => (
|
.map((post) => (
|
||||||
<div key={post.id} className="p-6 hover:bg-gray-50 transition-colors">
|
<div key={post.id} className="p-6 hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors">
|
||||||
<div className="flex items-start justify-between">
|
<div className="flex items-start justify-between">
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<div className="flex items-center space-x-2 mb-2">
|
<div className="flex items-center space-x-2 mb-2">
|
||||||
<h4 className="text-sm font-semibold text-gray-900">{post.authorName}</h4>
|
<h4 className="text-sm font-semibold text-gray-900 dark:text-gray-100">{post.authorName}</h4>
|
||||||
{post.isAcceptedAnswer && (
|
{post.isAcceptedAnswer && (
|
||||||
<div className="flex items-center space-x-1 bg-emerald-100 text-emerald-700 px-2 py-1 rounded-full text-xs">
|
<div className="flex items-center space-x-1 bg-emerald-100 dark:bg-emerald-900 text-emerald-700 dark:text-emerald-300 px-2 py-1 rounded-full text-xs">
|
||||||
<FaCheckCircle className="w-3 h-3" />
|
<FaCheckCircle className="w-3 h-3" />
|
||||||
<span>Accepted Answer</span>
|
<span>Accepted Answer</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -371,17 +371,17 @@ export function PostManagement({
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mb-3">
|
<div className="mb-3">
|
||||||
<p className="text-xs text-gray-500 mb-1">
|
<p className="text-xs text-gray-500 dark:text-gray-400 mb-1">
|
||||||
Reply to:{' '}
|
Reply to:{' '}
|
||||||
<span className="font-medium">{getTopicTitle(post.topicId)}</span>
|
<span className="font-medium">{getTopicTitle(post.topicId)}</span>
|
||||||
</p>
|
</p>
|
||||||
<p
|
<p
|
||||||
className="text-gray-700 line-clamp-3"
|
className="text-gray-700 dark:text-gray-200 line-clamp-3"
|
||||||
dangerouslySetInnerHTML={{ __html: post.content }}
|
dangerouslySetInnerHTML={{ __html: post.content }}
|
||||||
></p>
|
></p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center justify-between text-sm text-gray-500">
|
<div className="flex items-center justify-between text-sm text-gray-500 dark:text-gray-400">
|
||||||
<div className="flex items-center space-x-4">
|
<div className="flex items-center space-x-4">
|
||||||
<span>{formatDate(post.creationTime)}</span>
|
<span>{formatDate(post.creationTime)}</span>
|
||||||
<div className="flex items-center space-x-1">
|
<div className="flex items-center space-x-1">
|
||||||
|
|
@ -398,8 +398,8 @@ export function PostManagement({
|
||||||
onClick={() => handleToggleAcceptedAnswer(post)}
|
onClick={() => handleToggleAcceptedAnswer(post)}
|
||||||
className={`p-1 rounded-lg transition-colors ${
|
className={`p-1 rounded-lg transition-colors ${
|
||||||
post.isAcceptedAnswer
|
post.isAcceptedAnswer
|
||||||
? 'text-emerald-600 hover:bg-emerald-100'
|
? 'text-emerald-600 dark:text-emerald-400 hover:bg-emerald-100 dark:hover:bg-emerald-900'
|
||||||
: 'text-gray-400 hover:bg-gray-100'
|
: 'text-gray-400 dark:text-gray-500 hover:bg-gray-100 dark:hover:bg-gray-800'
|
||||||
}`}
|
}`}
|
||||||
title={
|
title={
|
||||||
post.isAcceptedAnswer
|
post.isAcceptedAnswer
|
||||||
|
|
@ -417,7 +417,7 @@ export function PostManagement({
|
||||||
<Button
|
<Button
|
||||||
size="xs"
|
size="xs"
|
||||||
onClick={() => handleEdit(post)}
|
onClick={() => handleEdit(post)}
|
||||||
className="p-1 text-blue-600 hover:bg-blue-100 rounded-lg transition-colors"
|
className="p-1 text-blue-600 dark:text-blue-400 hover:bg-blue-100 dark:hover:bg-blue-900 rounded-lg transition-colors"
|
||||||
title={translate('::App.Forum.PostManagement.EditPost')}
|
title={translate('::App.Forum.PostManagement.EditPost')}
|
||||||
>
|
>
|
||||||
<FaEdit className="w-3 h-3" />
|
<FaEdit className="w-3 h-3" />
|
||||||
|
|
@ -426,7 +426,7 @@ export function PostManagement({
|
||||||
<Button
|
<Button
|
||||||
size="xs"
|
size="xs"
|
||||||
onClick={() => confirmDeletePost(post)}
|
onClick={() => confirmDeletePost(post)}
|
||||||
className="p-1 text-red-600 hover:bg-red-100 rounded-lg transition-colors"
|
className="p-1 text-red-600 dark:text-red-400 hover:bg-red-100 dark:hover:bg-red-900 rounded-lg transition-colors"
|
||||||
title={translate('::App.Forum.PostManagement.DeletePost')}
|
title={translate('::App.Forum.PostManagement.DeletePost')}
|
||||||
>
|
>
|
||||||
<FaTrashAlt className="w-3 h-3" />
|
<FaTrashAlt className="w-3 h-3" />
|
||||||
|
|
|
||||||
|
|
@ -204,7 +204,7 @@ export function TopicManagement({
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
{/* Create/Edit Form */}
|
{/* Create/Edit Form */}
|
||||||
{showCreateForm && (
|
{showCreateForm && (
|
||||||
<div className="bg-white rounded-xl shadow-sm border border-gray-200 p-6">
|
<div className="bg-white dark:bg-gray-900 rounded-xl shadow-sm border border-gray-200 dark:border-gray-700 p-6">
|
||||||
<h3 className="text-lg font-semibold text-gray-900 mb-4">
|
<h3 className="text-lg font-semibold text-gray-900 mb-4">
|
||||||
{editingTopic
|
{editingTopic
|
||||||
? translate('::App.Forum.TopicManagement.EditTopic')
|
? translate('::App.Forum.TopicManagement.EditTopic')
|
||||||
|
|
@ -253,7 +253,7 @@ export function TopicManagement({
|
||||||
>
|
>
|
||||||
<Field
|
<Field
|
||||||
name="title"
|
name="title"
|
||||||
className="w-full border border-gray-300 rounded-lg px-3 py-2"
|
className="w-full border border-gray-300 dark:border-gray-700 rounded-lg px-3 py-2 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 placeholder-gray-400 dark:placeholder-gray-500"
|
||||||
/>
|
/>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
|
|
||||||
|
|
@ -266,7 +266,7 @@ export function TopicManagement({
|
||||||
<Field
|
<Field
|
||||||
as="select"
|
as="select"
|
||||||
name="categoryId"
|
name="categoryId"
|
||||||
className="w-full border border-gray-300 rounded-lg px-3 py-2"
|
className="w-full border border-gray-300 dark:border-gray-700 rounded-lg px-3 py-2 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100"
|
||||||
>
|
>
|
||||||
<option value="">Select a category</option>
|
<option value="">Select a category</option>
|
||||||
{categories.map((cat) => (
|
{categories.map((cat) => (
|
||||||
|
|
@ -287,7 +287,7 @@ export function TopicManagement({
|
||||||
as="textarea"
|
as="textarea"
|
||||||
name="content"
|
name="content"
|
||||||
rows={6}
|
rows={6}
|
||||||
className="w-full border border-gray-300 rounded-lg px-3 py-2"
|
className="w-full border border-gray-300 dark:border-gray-700 rounded-lg px-3 py-2 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 placeholder-gray-400 dark:placeholder-gray-500"
|
||||||
/>
|
/>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
|
|
||||||
|
|
@ -331,9 +331,9 @@ export function TopicManagement({
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Topics List */}
|
{/* Topics List */}
|
||||||
<div className="bg-white rounded-xl shadow-sm border border-gray-200 overflow-hidden">
|
<div className="bg-white dark:bg-gray-900 rounded-xl shadow-sm border border-gray-200 dark:border-gray-700 overflow-hidden">
|
||||||
<div className="flex items-center justify-between px-3 py-4 border-b border-gray-200">
|
<div className="flex items-center justify-between px-3 py-4 border-b border-gray-200 dark:border-gray-700">
|
||||||
<h3 className="text-lg font-semibold text-gray-900">
|
<h3 className="text-lg font-semibold text-gray-900 dark:text-gray-100">
|
||||||
{translate('::App.Forum.Dashboard.Topics')} ({topics.length})
|
{translate('::App.Forum.Dashboard.Topics')} ({topics.length})
|
||||||
</h3>
|
</h3>
|
||||||
<Button
|
<Button
|
||||||
|
|
@ -341,7 +341,7 @@ export function TopicManagement({
|
||||||
variant="solid"
|
variant="solid"
|
||||||
onClick={() => setShowCreateForm(true)}
|
onClick={() => setShowCreateForm(true)}
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
className="flex items-center space-x-2 bg-blue-600 px-4 py-2 rounded-lg hover:bg-blue-700 transition-colors disabled:opacity-50"
|
className="flex items-center space-x-2 bg-blue-600 dark:bg-blue-700 px-4 py-2 rounded-lg hover:bg-blue-700 dark:hover:bg-blue-800 transition-colors disabled:opacity-50"
|
||||||
>
|
>
|
||||||
<FaPlus className="w-4 h-4" />
|
<FaPlus className="w-4 h-4" />
|
||||||
<span>{translate('::App.Forum.TopicManagement.AddTopic')}</span>
|
<span>{translate('::App.Forum.TopicManagement.AddTopic')}</span>
|
||||||
|
|
@ -350,33 +350,33 @@ export function TopicManagement({
|
||||||
|
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<div className="p-8 text-center">
|
<div className="p-8 text-center">
|
||||||
<FaSpinner className="w-8 h-8 animate-spin mx-auto mb-4 text-blue-600" />
|
<FaSpinner className="w-8 h-8 animate-spin mx-auto mb-4 text-blue-600 dark:text-blue-400" />
|
||||||
<p className="text-gray-500">
|
<p className="text-gray-500 dark:text-gray-400">
|
||||||
{translate('::App.Forum.TopicManagement.Loadingtopics')}
|
{translate('::App.Forum.TopicManagement.Loadingtopics')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="divide-y divide-gray-200">
|
<div className="divide-y divide-gray-200 dark:divide-gray-800">
|
||||||
{topics
|
{topics
|
||||||
.sort(
|
.sort(
|
||||||
(a, b) => new Date(b.creationTime).getTime() - new Date(a.creationTime).getTime(),
|
(a, b) => new Date(b.creationTime).getTime() - new Date(a.creationTime).getTime(),
|
||||||
)
|
)
|
||||||
.map((topic) => (
|
.map((topic) => (
|
||||||
<div key={topic.id} className="p-6 hover:bg-gray-50 transition-colors">
|
<div key={topic.id} className="p-6 hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors">
|
||||||
<div className="flex items-start justify-between">
|
<div className="flex items-start justify-between">
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<div className="flex items-center space-x-2 mb-2">
|
<div className="flex items-center space-x-2 mb-2">
|
||||||
{topic.isPinned && <FaThumbtack className="w-4 h-4 text-orange-500" />}
|
{topic.isPinned && <FaThumbtack className="w-4 h-4 text-orange-500 dark:text-orange-400" />}
|
||||||
{topic.isLocked && <FaLock className="w-4 h-4 text-gray-400" />}
|
{topic.isLocked && <FaLock className="w-4 h-4 text-gray-400 dark:text-gray-500" />}
|
||||||
{topic.isSolved && <FaCheckCircle className="w-4 h-4 text-emerald-500" />}
|
{topic.isSolved && <FaCheckCircle className="w-4 h-4 text-emerald-500 dark:text-emerald-400" />}
|
||||||
<h4 className="text-lg font-semibold text-gray-900 line-clamp-1">
|
<h4 className="text-lg font-semibold text-gray-900 dark:text-gray-100 line-clamp-1">
|
||||||
{topic.title}
|
{topic.title}
|
||||||
</h4>
|
</h4>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p className="text-gray-600 mb-3 line-clamp-2">{topic.content}</p>
|
<p className="text-gray-600 dark:text-gray-300 mb-3 line-clamp-2">{topic.content}</p>
|
||||||
|
|
||||||
<div className="flex items-center justify-between text-sm text-gray-500">
|
<div className="flex items-center justify-between text-sm text-gray-500 dark:text-gray-400">
|
||||||
<div className="flex items-center space-x-4">
|
<div className="flex items-center space-x-4">
|
||||||
<span className="font-medium">{getCategoryName(topic.categoryId)}</span>
|
<span className="font-medium">{getCategoryName(topic.categoryId)}</span>
|
||||||
<span>by {topic.authorName}</span>
|
<span>by {topic.authorName}</span>
|
||||||
|
|
@ -399,8 +399,8 @@ export function TopicManagement({
|
||||||
onClick={() => handlePin(topic)}
|
onClick={() => handlePin(topic)}
|
||||||
className={`p-1 rounded-lg transition-colors ${
|
className={`p-1 rounded-lg transition-colors ${
|
||||||
topic.isPinned
|
topic.isPinned
|
||||||
? 'text-orange-600 hover:bg-orange-100'
|
? 'text-orange-600 dark:text-orange-400 hover:bg-orange-100 dark:hover:bg-orange-900'
|
||||||
: 'text-gray-400 hover:bg-gray-100'
|
: 'text-gray-400 dark:text-gray-500 hover:bg-gray-100 dark:hover:bg-gray-800'
|
||||||
}`}
|
}`}
|
||||||
title={topic.isPinned ? 'Unpin Topic' : 'Pin Topic'}
|
title={topic.isPinned ? 'Unpin Topic' : 'Pin Topic'}
|
||||||
>
|
>
|
||||||
|
|
@ -416,8 +416,8 @@ export function TopicManagement({
|
||||||
onClick={() => handleLock(topic)}
|
onClick={() => handleLock(topic)}
|
||||||
className={`p-1 rounded-lg transition-colors ${
|
className={`p-1 rounded-lg transition-colors ${
|
||||||
topic.isLocked
|
topic.isLocked
|
||||||
? 'text-yellow-600 hover:bg-yellow-100'
|
? 'text-yellow-600 dark:text-yellow-300 hover:bg-yellow-100 dark:hover:bg-yellow-900'
|
||||||
: 'text-green-600 hover:bg-green-100'
|
: 'text-green-600 dark:text-green-400 hover:bg-green-100 dark:hover:bg-green-900'
|
||||||
}`}
|
}`}
|
||||||
title={topic.isLocked ? 'Unlock Topic' : 'Lock Topic'}
|
title={topic.isLocked ? 'Unlock Topic' : 'Lock Topic'}
|
||||||
>
|
>
|
||||||
|
|
@ -433,8 +433,8 @@ export function TopicManagement({
|
||||||
onClick={() => handleSolved(topic)}
|
onClick={() => handleSolved(topic)}
|
||||||
className={`p-1 rounded-lg transition-colors ${
|
className={`p-1 rounded-lg transition-colors ${
|
||||||
topic.isSolved
|
topic.isSolved
|
||||||
? 'text-emerald-600 hover:bg-emerald-100'
|
? 'text-emerald-600 dark:text-emerald-400 hover:bg-emerald-100 dark:hover:bg-emerald-900'
|
||||||
: 'text-gray-400 hover:bg-gray-100'
|
: 'text-gray-400 dark:text-gray-500 hover:bg-gray-100 dark:hover:bg-gray-800'
|
||||||
}`}
|
}`}
|
||||||
title={topic.isSolved ? 'Mark as Unsolved' : 'Mark as Solved'}
|
title={topic.isSolved ? 'Mark as Unsolved' : 'Mark as Solved'}
|
||||||
>
|
>
|
||||||
|
|
@ -448,7 +448,7 @@ export function TopicManagement({
|
||||||
<Button
|
<Button
|
||||||
size="xs"
|
size="xs"
|
||||||
onClick={() => handleEdit(topic)}
|
onClick={() => handleEdit(topic)}
|
||||||
className="p-1 text-blue-600 hover:bg-blue-100 rounded-lg transition-colors"
|
className="p-1 text-blue-600 dark:text-blue-400 hover:bg-blue-100 dark:hover:bg-blue-900 rounded-lg transition-colors"
|
||||||
title={translate('::App.Forum.TopicManagement.EditTopic')}
|
title={translate('::App.Forum.TopicManagement.EditTopic')}
|
||||||
>
|
>
|
||||||
<FaEdit className="w-3 h-3" />
|
<FaEdit className="w-3 h-3" />
|
||||||
|
|
@ -457,7 +457,7 @@ export function TopicManagement({
|
||||||
<Button
|
<Button
|
||||||
size="xs"
|
size="xs"
|
||||||
onClick={() => confirmDeleteTopic(topic)}
|
onClick={() => confirmDeleteTopic(topic)}
|
||||||
className="p-1 text-red-600 hover:bg-red-100 rounded-lg transition-colors"
|
className="p-1 text-red-600 dark:text-red-400 hover:bg-red-100 dark:hover:bg-red-900 rounded-lg transition-colors"
|
||||||
title={translate('::App.Forum.TopicManagement.DeleteTopic')}
|
title={translate('::App.Forum.TopicManagement.DeleteTopic')}
|
||||||
>
|
>
|
||||||
<FaTrashAlt className="w-3 h-3" />
|
<FaTrashAlt className="w-3 h-3" />
|
||||||
|
|
|
||||||
|
|
@ -52,14 +52,14 @@ export function CreatePostModal({ onClose, onSubmit, parentPostId }: CreatePostM
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center p-4 z-50">
|
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center p-4 z-50">
|
||||||
<div className="bg-white rounded-xl shadow-xl max-w-2xl w-full max-h-[90vh] overflow-y-auto">
|
<div className="bg-white dark:bg-gray-900 rounded-xl shadow-xl max-w-2xl w-full max-h-[90vh] overflow-y-auto">
|
||||||
<div className="flex items-center justify-between p-6 border-b border-gray-200">
|
<div className="flex items-center justify-between p-6 border-b border-gray-200 dark:border-gray-700">
|
||||||
<h3 className="text-lg font-semibold text-gray-900">
|
<h3 className="text-lg font-semibold text-gray-900 dark:text-white">
|
||||||
{parentPostId
|
{parentPostId
|
||||||
? translate('::App.Forum.PostManagement.ReplytoTopic')
|
? translate('::App.Forum.PostManagement.ReplytoTopic')
|
||||||
: translate('::App.Forum.PostManagement.NewPost')}
|
: translate('::App.Forum.PostManagement.NewPost')}
|
||||||
</h3>
|
</h3>
|
||||||
<button onClick={onClose} className="text-gray-400 hover:text-gray-600 transition-colors">
|
<button onClick={onClose} className="text-gray-400 dark:text-gray-500 hover:text-gray-600 dark:hover:text-gray-300 transition-colors">
|
||||||
<FaTimes className="w-5 h-5" />
|
<FaTimes className="w-5 h-5" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -38,12 +38,12 @@ export function CreateTopicModal({ onClose, onSubmit }: CreateTopicModalProps) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center p-4 z-50">
|
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center p-4 z-50">
|
||||||
<div className="bg-white rounded-2xl shadow-2xl w-full max-w-lg max-h-[90vh] overflow-y-auto p-6">
|
<div className="bg-white dark:bg-gray-900 rounded-2xl shadow-2xl w-full max-w-lg max-h-[90vh] overflow-y-auto p-6">
|
||||||
<div className="flex items-center justify-between mb-4">
|
<div className="flex items-center justify-between mb-4">
|
||||||
<h3 className="text-lg font-semibold text-gray-900">
|
<h3 className="text-lg font-semibold text-gray-900 dark:text-white">
|
||||||
{translate('::App.Forum.TopicManagement.NewTopic')}
|
{translate('::App.Forum.TopicManagement.NewTopic')}
|
||||||
</h3>
|
</h3>
|
||||||
<button onClick={onClose} className="text-gray-400 hover:text-gray-600 transition-colors">
|
<button onClick={onClose} className="text-gray-400 dark:text-gray-500 hover:text-gray-600 dark:hover:text-gray-300 transition-colors">
|
||||||
<FaTimes className="w-5 h-5" />
|
<FaTimes className="w-5 h-5" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -68,7 +68,7 @@ export function CreateTopicModal({ onClose, onSubmit }: CreateTopicModalProps) {
|
||||||
name="title"
|
name="title"
|
||||||
placeholder="Başlık girin..."
|
placeholder="Başlık girin..."
|
||||||
autoFocus
|
autoFocus
|
||||||
className="w-full text-sm border border-gray-300 rounded-md px-3 py-2 focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
className="w-full text-sm border border-gray-300 dark:border-gray-700 rounded-md px-3 py-2 bg-white dark:bg-gray-800 text-gray-900 dark:text-white placeholder-gray-400 dark:placeholder-gray-500 focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||||
component={Input}
|
component={Input}
|
||||||
/>
|
/>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
|
|
@ -85,7 +85,7 @@ export function CreateTopicModal({ onClose, onSubmit }: CreateTopicModalProps) {
|
||||||
{...field}
|
{...field}
|
||||||
rows={6}
|
rows={6}
|
||||||
placeholder="Konu içeriğini yazın..."
|
placeholder="Konu içeriğini yazın..."
|
||||||
className="w-full text-sm border border-gray-300 rounded-md px-3 py-2 focus:ring-2 focus:ring-blue-500 focus:border-transparent resize-none"
|
className="w-full text-sm border border-gray-300 dark:border-gray-700 rounded-md px-3 py-2 bg-white dark:bg-gray-800 text-gray-900 dark:text-white placeholder-gray-400 dark:placeholder-gray-500 focus:ring-2 focus:ring-blue-500 focus:border-transparent resize-none"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Field>
|
</Field>
|
||||||
|
|
|
||||||
|
|
@ -24,20 +24,20 @@ export function ForumCategoryCard({ category, onClick }: CategoryCardProps) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
className="bg-white rounded-xl shadow-sm border border-gray-200 p-6 hover:shadow-md hover:border-blue-200 transition-all duration-200 cursor-pointer group"
|
className="bg-white dark:bg-gray-900 rounded-xl shadow-sm border border-gray-200 dark:border-gray-700 p-6 hover:shadow-md hover:border-blue-200 dark:hover:border-blue-500 transition-all duration-200 cursor-pointer group"
|
||||||
>
|
>
|
||||||
<div className="flex items-start justify-between">
|
<div className="flex items-start justify-between">
|
||||||
<div className="flex items-start space-x-4 flex-1">
|
<div className="flex items-start space-x-4 flex-1">
|
||||||
<div className="text-3xl">{category.icon}</div>
|
<div className="text-3xl">{category.icon}</div>
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<div className="flex items-center space-x-2 mb-1">
|
<div className="flex items-center space-x-2 mb-1">
|
||||||
<h3 className="text-lg font-semibold text-gray-900 group-hover:text-blue-600 transition-colors">
|
<h3 className="text-lg font-semibold text-gray-900 dark:text-white group-hover:text-blue-600 dark:group-hover:text-blue-400 transition-colors">
|
||||||
{category.name}
|
{category.name}
|
||||||
</h3>
|
</h3>
|
||||||
{category.isLocked && <FaLock className="w-4 h-4 text-gray-400" />}
|
{category.isLocked && <FaLock className="w-4 h-4 text-gray-400 dark:text-gray-500" />}
|
||||||
</div>
|
</div>
|
||||||
<p className="text-gray-600 text-sm mb-3 line-clamp-2">{category.description}</p>
|
<p className="text-gray-600 dark:text-gray-400 text-sm mb-3 line-clamp-2">{category.description}</p>
|
||||||
<div className="flex items-center space-x-4 text-sm text-gray-500">
|
<div className="flex items-center space-x-4 text-sm text-gray-500 dark:text-gray-400">
|
||||||
<div className="flex items-center space-x-1">
|
<div className="flex items-center space-x-1">
|
||||||
<FaComment className="w-4 h-4" />
|
<FaComment className="w-4 h-4" />
|
||||||
<span>{category.topicCount} topics</span>
|
<span>{category.topicCount} topics</span>
|
||||||
|
|
@ -49,9 +49,9 @@ export function ForumCategoryCard({ category, onClick }: CategoryCardProps) {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-right text-sm text-gray-500 ml-4">
|
<div className="text-right text-sm text-gray-500 dark:text-gray-400 ml-4">
|
||||||
<div>Last post</div>
|
<div>Last post</div>
|
||||||
<div className="font-medium text-gray-700">{formatDate(category.lastPostDate)}</div>
|
<div className="font-medium text-gray-700 dark:text-gray-200">{formatDate(category.lastPostDate)}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ export function ForumPostCard({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`bg-white rounded-xl shadow-sm border border-gray-200 p-6 ${isFirst ? 'border-l-4 border-l-blue-500' : ''}`}
|
className={`bg-white dark:bg-gray-900 rounded-xl shadow-sm border border-gray-200 dark:border-gray-700 p-6 ${isFirst ? 'border-l-4 border-l-blue-500 dark:border-l-blue-400' : ''}`}
|
||||||
>
|
>
|
||||||
<div className="flex items-start space-x-4">
|
<div className="flex items-start space-x-4">
|
||||||
<div className="flex-shrink-0">
|
<div className="flex-shrink-0">
|
||||||
|
|
@ -45,20 +45,20 @@ export function ForumPostCard({
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<div className="flex items-center justify-between mb-2">
|
<div className="flex items-center justify-between mb-2">
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
<h4 className="text-sm font-semibold text-gray-900">{post.authorName}</h4>
|
<h4 className="text-sm font-semibold text-gray-900 dark:text-white">{post.authorName}</h4>
|
||||||
{post.isAcceptedAnswer && (
|
{post.isAcceptedAnswer && (
|
||||||
<div className="flex items-center space-x-1 bg-emerald-100 text-emerald-700 px-2 py-1 rounded-full text-xs">
|
<div className="flex items-center space-x-1 bg-emerald-100 dark:bg-emerald-900 text-emerald-700 dark:text-emerald-200 px-2 py-1 rounded-full text-xs">
|
||||||
<FaCheckCircle className="w-3 h-3" />
|
<FaCheckCircle className="w-3 h-3" />
|
||||||
<span>{translate('::App.Forum.PostManagement.AcceptedAnswer')}</span>
|
<span>{translate('::App.Forum.PostManagement.AcceptedAnswer')}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<span className="text-sm text-gray-500">{formatDate(post.creationTime)}</span>
|
<span className="text-sm text-gray-500 dark:text-gray-400">{formatDate(post.creationTime)}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="prose prose-sm max-w-none mb-4">
|
<div className="prose prose-sm max-w-none mb-4">
|
||||||
<p
|
<p
|
||||||
className="text-gray-700 whitespace-pre-wrap"
|
className="text-gray-700 dark:text-gray-300 whitespace-pre-wrap"
|
||||||
dangerouslySetInnerHTML={{ __html: post.content }}
|
dangerouslySetInnerHTML={{ __html: post.content }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -68,8 +68,8 @@ export function ForumPostCard({
|
||||||
onClick={() => onLike(post.id, isFirst)}
|
onClick={() => onLike(post.id, isFirst)}
|
||||||
className={`flex items-center space-x-1 px-3 py-1 rounded-full text-sm transition-colors ${
|
className={`flex items-center space-x-1 px-3 py-1 rounded-full text-sm transition-colors ${
|
||||||
isLiked
|
isLiked
|
||||||
? 'bg-red-100 text-red-600 hover:bg-red-200'
|
? 'bg-red-100 dark:bg-red-900 text-red-600 dark:text-red-200 hover:bg-red-200 dark:hover:bg-red-800'
|
||||||
: 'bg-gray-100 text-gray-600 hover:bg-gray-200'
|
: 'bg-gray-100 dark:bg-gray-800 text-gray-600 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-700'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<FaHeart className={`w-4 h-4 ${isLiked ? 'fill-current' : ''}`} />
|
<FaHeart className={`w-4 h-4 ${isLiked ? 'fill-current' : ''}`} />
|
||||||
|
|
@ -79,7 +79,7 @@ export function ForumPostCard({
|
||||||
{!isFirst && (
|
{!isFirst && (
|
||||||
<button
|
<button
|
||||||
onClick={() => onReply(post.id)}
|
onClick={() => onReply(post.id)}
|
||||||
className="flex items-center space-x-1 px-3 py-1 rounded-full text-sm bg-gray-100 text-gray-600 hover:bg-gray-200 transition-colors"
|
className="flex items-center space-x-1 px-3 py-1 rounded-full text-sm bg-gray-100 dark:bg-gray-800 text-gray-600 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors"
|
||||||
>
|
>
|
||||||
<FaReply className="w-4 h-4" />
|
<FaReply className="w-4 h-4" />
|
||||||
<span>{translate('::App.Forum.PostManagement.PostReply')}</span>
|
<span>{translate('::App.Forum.PostManagement.PostReply')}</span>
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ export function ForumTopicCard({ topic, onClick }: TopicCardProps) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
className="bg-white rounded-xl shadow-sm border border-gray-200 p-6 hover:shadow-md hover:border-blue-200 transition-all duration-200 cursor-pointer group"
|
className="bg-white dark:bg-gray-900 rounded-xl shadow-sm border border-gray-200 dark:border-gray-700 p-6 hover:shadow-md hover:border-blue-200 dark:hover:border-blue-500 transition-all duration-200 cursor-pointer group"
|
||||||
>
|
>
|
||||||
<div className="flex items-start justify-between">
|
<div className="flex items-start justify-between">
|
||||||
{/* Sol taraf: Başlık, içerik, istatistik */}
|
{/* Sol taraf: Başlık, içerik, istatistik */}
|
||||||
|
|
@ -36,14 +36,14 @@ export function ForumTopicCard({ topic, onClick }: TopicCardProps) {
|
||||||
{topic.isPinned && <FaThumbtack className="w-4 h-4 text-orange-500" />}
|
{topic.isPinned && <FaThumbtack className="w-4 h-4 text-orange-500" />}
|
||||||
{topic.isLocked && <FaLock className="w-4 h-4 text-gray-400" />}
|
{topic.isLocked && <FaLock className="w-4 h-4 text-gray-400" />}
|
||||||
{topic.isSolved && <FaCheckCircle className="w-4 h-4 text-emerald-500" />}
|
{topic.isSolved && <FaCheckCircle className="w-4 h-4 text-emerald-500" />}
|
||||||
<h3 className="text-lg font-semibold text-gray-900 group-hover:text-blue-600 transition-colors line-clamp-1">
|
<h3 className="text-lg font-semibold text-gray-900 dark:text-white group-hover:text-blue-600 dark:group-hover:text-blue-400 transition-colors line-clamp-1">
|
||||||
{topic.title}
|
{topic.title}
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p className="text-gray-600 text-sm mb-4 line-clamp-2">{topic.content}</p>
|
<p className="text-gray-600 dark:text-gray-400 text-sm mb-4 line-clamp-2">{topic.content}</p>
|
||||||
|
|
||||||
<div className="flex items-center space-x-4 text-sm text-gray-500">
|
<div className="flex items-center space-x-4 text-sm text-gray-500 dark:text-gray-400">
|
||||||
<div className="flex items-center space-x-1" title={translate('::App.Platform.Views')}>
|
<div className="flex items-center space-x-1" title={translate('::App.Platform.Views')}>
|
||||||
<FaEye className="w-4 h-4" />
|
<FaEye className="w-4 h-4" />
|
||||||
<span>{topic.viewCount}</span>
|
<span>{topic.viewCount}</span>
|
||||||
|
|
@ -70,17 +70,17 @@ export function ForumTopicCard({ topic, onClick }: TopicCardProps) {
|
||||||
alt="User"
|
alt="User"
|
||||||
className="w-10 h-10 rounded-full border"
|
className="w-10 h-10 rounded-full border"
|
||||||
/>
|
/>
|
||||||
<div className="text-sm font-medium text-gray-700">{topic.authorName}</div>
|
<div className="text-sm font-medium text-gray-700 dark:text-gray-200">{topic.authorName}</div>
|
||||||
<div className="text-xs text-gray-500">{formatDate(topic.creationTime)}</div>
|
<div className="text-xs text-gray-500 dark:text-gray-400">{formatDate(topic.creationTime)}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{topic.lastPostDate && topic.lastPostUserName && (
|
{topic.lastPostDate && topic.lastPostUserName && (
|
||||||
<div className="mt-4 pt-4 border-t border-gray-100">
|
<div className="mt-4 pt-4 border-t border-gray-100 dark:border-gray-700">
|
||||||
<div className="flex items-center justify-between text-sm text-gray-500">
|
<div className="flex items-center justify-between text-sm text-gray-500 dark:text-gray-400">
|
||||||
<span>
|
<span>
|
||||||
{translate('::App.Forum.TopicManagement.Lastreplyby')}{' '}
|
{translate('::App.Forum.TopicManagement.Lastreplyby')}{' '}
|
||||||
<span className="font-medium text-gray-700">{topic.lastPostUserName}</span>
|
<span className="font-medium text-gray-700 dark:text-gray-200">{topic.lastPostUserName}</span>
|
||||||
{' '}
|
{' '}
|
||||||
<span>{formatDate(topic.lastPostDate)}</span>
|
<span>{formatDate(topic.lastPostDate)}</span>
|
||||||
</span>
|
</span>
|
||||||
|
|
|
||||||
|
|
@ -248,9 +248,9 @@ export function ForumView({
|
||||||
onReply={handleReply}
|
onReply={handleReply}
|
||||||
isLiked={likedPosts.has(post.id)}
|
isLiked={likedPosts.has(post.id)}
|
||||||
/>
|
/>
|
||||||
{post.children.length > 0 && (
|
{post.children.length > 0 && (
|
||||||
<div className="pl-6 border-gray-200 mt-4">{renderPosts(post.children)}</div>
|
<div className="pl-6 border-gray-200 dark:border-gray-700 mt-4">{renderPosts(post.children)}</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
@ -294,7 +294,7 @@ export function ForumView({
|
||||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||||
<div className="flex items-center justify-center h-64">
|
<div className="flex items-center justify-center h-64">
|
||||||
<FaSpinner className="w-8 h-8 animate-spin text-blue-600" />
|
<FaSpinner className="w-8 h-8 animate-spin text-blue-600" />
|
||||||
<span className="ml-2 text-gray-600">Loading forum data...</span>
|
<span className="ml-2 text-gray-600 dark:text-gray-300">Loading forum data...</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
@ -306,27 +306,27 @@ export function ForumView({
|
||||||
<div className="flex items-center justify-between mb-4">
|
<div className="flex items-center justify-between mb-4">
|
||||||
{/* Left Side: Breadcrumb */}
|
{/* Left Side: Breadcrumb */}
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
{viewState !== 'categories' && <FaArrowLeft className="w-4 h-4" />}
|
{viewState !== 'categories' && <FaArrowLeft className="w-4 h-4 text-gray-700 dark:text-gray-200" />}
|
||||||
<nav className="flex items-center space-x-2 text-sm text-gray-500">
|
<nav className="flex items-center space-x-2 text-sm text-gray-500 dark:text-gray-400">
|
||||||
{selectedCategory && (
|
{selectedCategory && (
|
||||||
<>
|
<>
|
||||||
<button
|
<button
|
||||||
onClick={() => handleBreadcrumbClick('forum')}
|
onClick={() => handleBreadcrumbClick('forum')}
|
||||||
className={`transition-colors ${
|
className={`transition-colors ${
|
||||||
viewState === 'categories'
|
viewState === 'categories'
|
||||||
? 'text-gray-900 font-medium cursor-default'
|
? 'text-gray-900 dark:text-gray-100 font-medium cursor-default'
|
||||||
: 'hover:text-blue-600 cursor-pointer'
|
: 'hover:text-blue-600 dark:hover:text-blue-400 cursor-pointer'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<div className="text-sm font-medium text-gray-900">Forum</div>
|
<div className="text-sm font-medium text-gray-900 dark:text-gray-100">Forum</div>
|
||||||
</button>
|
</button>
|
||||||
<span>/</span>
|
<span>/</span>
|
||||||
<button
|
<button
|
||||||
onClick={() => handleBreadcrumbClick('category')}
|
onClick={() => handleBreadcrumbClick('category')}
|
||||||
className={`transition-colors ${
|
className={`transition-colors ${
|
||||||
viewState === 'topics'
|
viewState === 'topics'
|
||||||
? 'text-gray-900 font-medium cursor-default'
|
? 'text-gray-900 dark:text-gray-100 font-medium cursor-default'
|
||||||
: 'hover:text-blue-600 cursor-pointer'
|
: 'hover:text-blue-600 dark:hover:text-blue-400 cursor-pointer'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{selectedCategory.name}
|
{selectedCategory.name}
|
||||||
|
|
@ -336,7 +336,7 @@ export function ForumView({
|
||||||
{selectedTopic && (
|
{selectedTopic && (
|
||||||
<>
|
<>
|
||||||
<span>/</span>
|
<span>/</span>
|
||||||
<span className="text-gray-900 font-medium">{selectedTopic.title}</span>
|
<span className="text-gray-900 dark:text-gray-100 font-medium">{selectedTopic.title}</span>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</nav>
|
</nav>
|
||||||
|
|
@ -350,7 +350,7 @@ export function ForumView({
|
||||||
icon={<FaPlus className="w-4 h-4" />}
|
icon={<FaPlus className="w-4 h-4" />}
|
||||||
variant="solid"
|
variant="solid"
|
||||||
onClick={() => setShowCreateTopic(true)}
|
onClick={() => setShowCreateTopic(true)}
|
||||||
className="flex items-center space-x-2 bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700 transition-colors"
|
className="flex items-center space-x-2 bg-blue-600 dark:bg-blue-700 text-white px-4 py-2 rounded-lg hover:bg-blue-700 dark:hover:bg-blue-800 transition-colors"
|
||||||
>
|
>
|
||||||
<span>{translate('::App.Forum.TopicManagement.NewTopic')}</span>
|
<span>{translate('::App.Forum.TopicManagement.NewTopic')}</span>
|
||||||
</Button>
|
</Button>
|
||||||
|
|
@ -361,7 +361,7 @@ export function ForumView({
|
||||||
icon={<FaPlus className="w-4 h-4" />}
|
icon={<FaPlus className="w-4 h-4" />}
|
||||||
variant="solid"
|
variant="solid"
|
||||||
onClick={() => setShowCreatePost(true)}
|
onClick={() => setShowCreatePost(true)}
|
||||||
className="flex items-center space-x-2 bg-emerald-600 text-white px-4 py-2 rounded-lg hover:bg-emerald-700 transition-colors"
|
className="flex items-center space-x-2 bg-emerald-600 dark:bg-emerald-700 text-white px-4 py-2 rounded-lg hover:bg-emerald-700 dark:hover:bg-emerald-800 transition-colors"
|
||||||
>
|
>
|
||||||
<span>{translate('::App.Forum.PostManagement.NewPost')}</span>
|
<span>{translate('::App.Forum.PostManagement.NewPost')}</span>
|
||||||
</Button>
|
</Button>
|
||||||
|
|
@ -373,9 +373,9 @@ export function ForumView({
|
||||||
icon={<FaSearch className="w-4 h-4" />}
|
icon={<FaSearch className="w-4 h-4" />}
|
||||||
onClick={() => setIsSearchModalOpen(true)}
|
onClick={() => setIsSearchModalOpen(true)}
|
||||||
variant="default"
|
variant="default"
|
||||||
className="hidden md:flex items-center space-x-2 px-4 py-2 border border-gray-300 rounded-lg hover:bg-gray-50 transition-colors"
|
className="hidden md:flex items-center space-x-2 px-4 py-2 border border-gray-300 dark:border-gray-700 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors"
|
||||||
>
|
>
|
||||||
<span className="text-gray-500">
|
<span className="text-gray-500 dark:text-gray-300">
|
||||||
{translate('::App.Forum.TopicManagement.Searchtopics')}
|
{translate('::App.Forum.TopicManagement.Searchtopics')}
|
||||||
</span>
|
</span>
|
||||||
</Button>
|
</Button>
|
||||||
|
|
@ -385,7 +385,7 @@ export function ForumView({
|
||||||
icon={<FaSearch className="w-5 h-5" />}
|
icon={<FaSearch className="w-5 h-5" />}
|
||||||
onClick={() => setIsSearchModalOpen(true)}
|
onClick={() => setIsSearchModalOpen(true)}
|
||||||
variant="default"
|
variant="default"
|
||||||
className="md:hidden p-2 text-gray-400 hover:text-gray-600 transition-colors"
|
className="md:hidden p-2 text-gray-400 dark:text-gray-300 hover:text-gray-600 dark:hover:text-gray-100 transition-colors"
|
||||||
></Button>
|
></Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -97,21 +97,21 @@ export function SearchModal({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-start justify-center pt-20 p-4 z-50">
|
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-start justify-center pt-20 p-4 z-50">
|
||||||
<div className="bg-white rounded-xl shadow-xl max-w-2xl w-full max-h-[70vh] overflow-hidden">
|
<div className="bg-white dark:bg-gray-900 rounded-xl shadow-xl max-w-2xl w-full max-h-[70vh] overflow-hidden">
|
||||||
<div className="flex items-center p-4 border-b border-gray-200">
|
<div className="flex items-center p-4 border-b border-gray-200 dark:border-gray-700">
|
||||||
<FaSearch className="w-5 h-5 text-gray-400 mr-3" />
|
<FaSearch className="w-5 h-5 text-gray-400 dark:text-gray-500 mr-3" />
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={searchQuery}
|
value={searchQuery}
|
||||||
onChange={(e) => setSearchQuery(e.target.value)}
|
onChange={(e) => setSearchQuery(e.target.value)}
|
||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
placeholder="Search categories, topics, and posts..."
|
placeholder="Search categories, topics, and posts..."
|
||||||
className="flex-1 outline-none text-lg"
|
className="flex-1 outline-none text-lg bg-transparent text-gray-900 dark:text-white placeholder-gray-400 dark:placeholder-gray-500"
|
||||||
autoFocus
|
autoFocus
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
className="text-gray-400 hover:text-gray-600 transition-colors ml-3"
|
className="text-gray-400 dark:text-gray-500 hover:text-gray-600 dark:hover:text-gray-300 transition-colors ml-3"
|
||||||
>
|
>
|
||||||
<FaTimes className="w-5 h-5" />
|
<FaTimes className="w-5 h-5" />
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -119,12 +119,12 @@ export function SearchModal({
|
||||||
|
|
||||||
<div className="overflow-y-auto max-h-96">
|
<div className="overflow-y-auto max-h-96">
|
||||||
{!searchQuery.trim() ? (
|
{!searchQuery.trim() ? (
|
||||||
<div className="p-8 text-center text-gray-500">
|
<div className="p-8 text-center text-gray-500 dark:text-gray-400">
|
||||||
<FaSearch className="w-12 h-12 mx-auto mb-4 text-gray-300" />
|
<FaSearch className="w-12 h-12 mx-auto mb-4 text-gray-300 dark:text-gray-600" />
|
||||||
<p>Start typing to search categories, topics, and posts...</p>
|
<p>Start typing to search categories, topics, and posts...</p>
|
||||||
</div>
|
</div>
|
||||||
) : !hasResults ? (
|
) : !hasResults ? (
|
||||||
<div className="p-8 text-center text-gray-500">
|
<div className="p-8 text-center text-gray-500 dark:text-gray-400">
|
||||||
<p>No results found for "{searchQuery}"</p>
|
<p>No results found for "{searchQuery}"</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
|
|
@ -132,7 +132,7 @@ export function SearchModal({
|
||||||
{/* Categories */}
|
{/* Categories */}
|
||||||
{searchResults.categories.length > 0 && (
|
{searchResults.categories.length > 0 && (
|
||||||
<div>
|
<div>
|
||||||
<div className="px-4 py-2 text-xs font-semibold text-gray-500 uppercase tracking-wide bg-gray-50">
|
<div className="px-4 py-2 text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wide bg-gray-50 dark:bg-gray-800">
|
||||||
Categories ({searchResults.categories.length})
|
Categories ({searchResults.categories.length})
|
||||||
</div>
|
</div>
|
||||||
{searchResults.categories.map((category, index) => (
|
{searchResults.categories.map((category, index) => (
|
||||||
|
|
@ -142,24 +142,24 @@ export function SearchModal({
|
||||||
onCategorySelect(category)
|
onCategorySelect(category)
|
||||||
onClose()
|
onClose()
|
||||||
}}
|
}}
|
||||||
className={`w-full flex items-center px-4 py-3 hover:bg-gray-50 transition-colors ${
|
className={`w-full flex items-center px-4 py-3 hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors ${
|
||||||
selectedIndex === index ? 'bg-blue-50 border-r-2 border-blue-500' : ''
|
selectedIndex === index ? 'bg-blue-50 dark:bg-blue-900 border-r-2 border-blue-500 dark:border-blue-400' : ''
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<div className="flex items-center space-x-3 flex-1">
|
<div className="flex items-center space-x-3 flex-1">
|
||||||
<div className="flex-shrink-0">
|
<div className="flex-shrink-0">
|
||||||
<div className="w-8 h-8 bg-blue-100 rounded-lg flex items-center justify-center">
|
<div className="w-8 h-8 bg-blue-100 dark:bg-blue-900 rounded-lg flex items-center justify-center">
|
||||||
<FaFolder className="w-4 h-4 text-blue-600" />
|
<FaFolder className="w-4 h-4 text-blue-600 dark:text-blue-400" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-left">
|
<div className="text-left">
|
||||||
<div className="font-medium text-gray-900">{category.name}</div>
|
<div className="font-medium text-gray-900 dark:text-white">{category.name}</div>
|
||||||
<div className="text-sm text-gray-500 line-clamp-1">
|
<div className="text-sm text-gray-500 dark:text-gray-400 line-clamp-1">
|
||||||
{category.description}
|
{category.description}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-xs text-gray-400">{category.topicCount} topics</div>
|
<div className="text-xs text-gray-400 dark:text-gray-500">{category.topicCount} topics</div>
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -168,7 +168,7 @@ export function SearchModal({
|
||||||
{/* Topics */}
|
{/* Topics */}
|
||||||
{searchResults.topics.length > 0 && (
|
{searchResults.topics.length > 0 && (
|
||||||
<div>
|
<div>
|
||||||
<div className="px-4 py-2 text-xs font-semibold text-gray-500 uppercase tracking-wide bg-gray-50">
|
<div className="px-4 py-2 text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wide bg-gray-50 dark:bg-gray-800">
|
||||||
Topics ({searchResults.topics.length})
|
Topics ({searchResults.topics.length})
|
||||||
</div>
|
</div>
|
||||||
{searchResults.topics.map((topic, index) => {
|
{searchResults.topics.map((topic, index) => {
|
||||||
|
|
@ -180,28 +180,28 @@ export function SearchModal({
|
||||||
onTopicSelect(topic)
|
onTopicSelect(topic)
|
||||||
onClose()
|
onClose()
|
||||||
}}
|
}}
|
||||||
className={`w-full flex items-center px-4 py-3 hover:bg-gray-50 transition-colors ${
|
className={`w-full flex items-center px-4 py-3 hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors ${
|
||||||
selectedIndex === globalIndex
|
selectedIndex === globalIndex
|
||||||
? 'bg-blue-50 border-r-2 border-blue-500'
|
? 'bg-blue-50 dark:bg-blue-900 border-r-2 border-blue-500 dark:border-blue-400'
|
||||||
: ''
|
: ''
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<div className="flex items-center space-x-3 flex-1">
|
<div className="flex items-center space-x-3 flex-1">
|
||||||
<div className="flex-shrink-0">
|
<div className="flex-shrink-0">
|
||||||
<div className="w-8 h-8 bg-emerald-100 rounded-lg flex items-center justify-center">
|
<div className="w-8 h-8 bg-emerald-100 dark:bg-emerald-900 rounded-lg flex items-center justify-center">
|
||||||
<FaRegComment className="w-4 h-4 text-emerald-600" />
|
<FaRegComment className="w-4 h-4 text-emerald-600 dark:text-emerald-400" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-left">
|
<div className="text-left">
|
||||||
<div className="font-medium text-gray-900 line-clamp-1">
|
<div className="font-medium text-gray-900 dark:text-white line-clamp-1">
|
||||||
{topic.title}
|
{topic.title}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-sm text-gray-500">
|
<div className="text-sm text-gray-500 dark:text-gray-400">
|
||||||
by {topic.authorName} • {formatDate(topic.creationTime)}
|
by {topic.authorName} • {formatDate(topic.creationTime)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-xs text-gray-400">{topic.replyCount} replies</div>
|
<div className="text-xs text-gray-400 dark:text-gray-500">{topic.replyCount} replies</div>
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
|
|
@ -211,7 +211,7 @@ export function SearchModal({
|
||||||
{/* Posts */}
|
{/* Posts */}
|
||||||
{searchResults.posts.length > 0 && (
|
{searchResults.posts.length > 0 && (
|
||||||
<div>
|
<div>
|
||||||
<div className="px-4 py-2 text-xs font-semibold text-gray-500 uppercase tracking-wide bg-gray-50">
|
<div className="px-4 py-2 text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wide bg-gray-50 dark:bg-gray-800">
|
||||||
Posts ({searchResults.posts.length})
|
Posts ({searchResults.posts.length})
|
||||||
</div>
|
</div>
|
||||||
{searchResults.posts.map((post, index) => {
|
{searchResults.posts.map((post, index) => {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import React, { useState, useRef } from 'react'
|
import React, { useState, useRef } from 'react'
|
||||||
import { motion, AnimatePresence } from 'framer-motion'
|
import { motion, AnimatePresence } from 'framer-motion'
|
||||||
import classNames from 'classnames'
|
import classNames from 'classnames'
|
||||||
import EmojiPicker, { EmojiClickData } from 'emoji-picker-react'
|
import EmojiPicker, { EmojiClickData, Theme } from 'emoji-picker-react'
|
||||||
import { FaChartBar, FaSmile, FaTimes, FaImages, FaMapMarkerAlt } from 'react-icons/fa'
|
import { FaChartBar, FaSmile, FaTimes, FaImages, FaMapMarkerAlt } from 'react-icons/fa'
|
||||||
import MediaManager from './MediaManager'
|
import MediaManager from './MediaManager'
|
||||||
import LocationPicker from './LocationPicker'
|
import LocationPicker from './LocationPicker'
|
||||||
|
|
@ -41,6 +41,7 @@ const CreatePost: React.FC<CreatePostProps> = ({ onCreatePost }) => {
|
||||||
const textareaRef = useRef<HTMLTextAreaElement>(null)
|
const textareaRef = useRef<HTMLTextAreaElement>(null)
|
||||||
const emojiPickerRef = useRef<HTMLDivElement>(null)
|
const emojiPickerRef = useRef<HTMLDivElement>(null)
|
||||||
const { user, tenant } = useStoreState((state) => state.auth)
|
const { user, tenant } = useStoreState((state) => state.auth)
|
||||||
|
const theme = useStoreState((state) => state.theme)
|
||||||
|
|
||||||
const handleSubmit = (e: React.FormEvent) => {
|
const handleSubmit = (e: React.FormEvent) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
|
@ -445,8 +446,14 @@ const CreatePost: React.FC<CreatePostProps> = ({ onCreatePost }) => {
|
||||||
|
|
||||||
{/* Emoji Picker */}
|
{/* Emoji Picker */}
|
||||||
{showEmojiPicker && (
|
{showEmojiPicker && (
|
||||||
<div ref={emojiPickerRef} className="absolute bottom-12 left-0 z-50">
|
<div ref={emojiPickerRef} className="absolute bottom-6 left-0 z-50 bg-white dark:bg-gray-800 rounded-lg shadow-lg border border-gray-200 dark:border-gray-700 p-2">
|
||||||
<EmojiPicker onEmojiClick={handleEmojiClick} autoFocusSearch={false} />
|
<EmojiPicker
|
||||||
|
searchDisabled
|
||||||
|
theme={theme.mode === 'dark' ? Theme.DARK : Theme.LIGHT}
|
||||||
|
height={350}
|
||||||
|
onEmojiClick={handleEmojiClick}
|
||||||
|
autoFocusSearch={false}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -392,11 +392,11 @@ const LocationPicker: React.FC<LocationPickerProps> = ({ onSelect, onClose }) =>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Footer */}
|
{/* Footer */}
|
||||||
<div className="flex items-center justify-between p-4 border-t border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-750">
|
<div className="flex items-center justify-between p-4 border-t border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800">
|
||||||
<div className="text-sm text-gray-600 dark:text-gray-400">
|
<div className="text-sm text-gray-600 dark:text-gray-400">
|
||||||
{selectedLocation ? (
|
{selectedLocation ? (
|
||||||
<span className="flex items-center gap-2">
|
<span className="flex items-center gap-2">
|
||||||
<FaMapMarkerAlt className="w-4 h-4 text-blue-600" />
|
<FaMapMarkerAlt className="w-4 h-4 text-blue-600 dark:text-blue-400" />
|
||||||
<span className="font-medium text-gray-900 dark:text-gray-100">
|
<span className="font-medium text-gray-900 dark:text-gray-100">
|
||||||
{selectedLocation.name}
|
{selectedLocation.name}
|
||||||
</span>
|
</span>
|
||||||
|
|
@ -410,14 +410,14 @@ const LocationPicker: React.FC<LocationPickerProps> = ({ onSelect, onClose }) =>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<button
|
<button
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
className="px-4 py-2 text-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-700 rounded-lg transition-colors"
|
className="px-4 py-2 text-gray-700 dark:text-gray-200 hover:bg-gray-200 dark:hover:bg-gray-700 rounded-lg transition-colors"
|
||||||
>
|
>
|
||||||
{translate('::Cancel')}
|
{translate('::Cancel')}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={handleConfirm}
|
onClick={handleConfirm}
|
||||||
disabled={!selectedLocation}
|
disabled={!selectedLocation}
|
||||||
className="px-6 py-2 bg-blue-600 text-white font-medium rounded-lg hover:bg-blue-700 disabled:bg-gray-400 disabled:cursor-not-allowed transition-colors"
|
className="px-6 py-2 bg-blue-600 dark:bg-blue-700 text-white font-medium rounded-lg hover:bg-blue-700 dark:hover:bg-blue-800 disabled:bg-gray-400 dark:disabled:bg-gray-700 disabled:cursor-not-allowed transition-colors"
|
||||||
>
|
>
|
||||||
{translate('::ListForms.Wizard.Add')}
|
{translate('::ListForms.Wizard.Add')}
|
||||||
</button>
|
</button>
|
||||||
|
|
|
||||||
|
|
@ -16,17 +16,17 @@ const Surveys: React.FC<SurveysProps> = ({ surveys, onTakeSurvey }) => {
|
||||||
const { translate } = useLocalization()
|
const { translate } = useLocalization()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="bg-gradient-to-br from-white to-gray-50 dark:from-gray-800 dark:to-gray-850 rounded-xl shadow-lg border border-gray-200/50 dark:border-gray-700/50 overflow-hidden">
|
<div className="bg-gradient-to-br from-white to-gray-50 dark:from-gray-800 dark:to-gray-900 rounded-xl shadow-lg border border-gray-200/50 dark:border-gray-700/50 overflow-hidden">
|
||||||
{/* Header with gradient */}
|
{/* Header with gradient */}
|
||||||
|
|
||||||
<div className="p-4 border-b border-gray-200 dark:border-gray-700">
|
<div className="p-4 border-b border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800">
|
||||||
<h2 className="text-base font-semibold text-gray-900 dark:text-white flex items-center gap-2">
|
<h2 className="text-base font-semibold text-gray-900 dark:text-white flex items-center gap-2">
|
||||||
<FaClipboardCheck className="w-5 h-5" />
|
<FaClipboardCheck className="w-5 h-5" />
|
||||||
{translate('::App.Platform.Intranet.Widgets.ActiveSurveys.Title')}
|
{translate('::App.Platform.Intranet.Widgets.ActiveSurveys.Title')}
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="p-3 space-y-4">
|
<div className="p-3 space-y-4 bg-white dark:bg-gray-800">
|
||||||
{surveys?.map((survey, index) => {
|
{surveys?.map((survey, index) => {
|
||||||
const daysLeft = dayjs(survey.deadline).diff(dayjs(), 'day')
|
const daysLeft = dayjs(survey.deadline).diff(dayjs(), 'day')
|
||||||
const urgency = daysLeft <= 3 ? 'urgent' : daysLeft <= 7 ? 'warning' : 'normal'
|
const urgency = daysLeft <= 3 ? 'urgent' : daysLeft <= 7 ? 'warning' : 'normal'
|
||||||
|
|
@ -38,15 +38,15 @@ const Surveys: React.FC<SurveysProps> = ({ surveys, onTakeSurvey }) => {
|
||||||
onClick={() => onTakeSurvey(survey)}
|
onClick={() => onTakeSurvey(survey)}
|
||||||
className={`group relative p-5 rounded-xl border cursor-pointer transition-all duration-300 hover:shadow-lg hover:-translate-y-1 ${
|
className={`group relative p-5 rounded-xl border cursor-pointer transition-all duration-300 hover:shadow-lg hover:-translate-y-1 ${
|
||||||
isCompleted
|
isCompleted
|
||||||
? 'bg-green-50 dark:bg-green-900/20 border-green-300 dark:border-green-700 hover:border-green-400 dark:hover:border-green-500'
|
? 'bg-green-50 dark:bg-green-900/30 border-green-300 dark:border-green-700 hover:border-green-400 dark:hover:border-green-500'
|
||||||
: 'bg-white dark:bg-gray-750 border-gray-200 dark:border-gray-600 hover:border-purple-300 dark:hover:border-purple-500'
|
: 'bg-white dark:bg-gray-800 border-gray-200 dark:border-gray-700 hover:border-purple-300 dark:hover:border-purple-500'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{/* Background gradient on hover */}
|
{/* Background gradient on hover */}
|
||||||
<div className={`absolute inset-0 rounded-xl opacity-0 group-hover:opacity-100 transition-opacity duration-300 ${
|
<div className={`absolute inset-0 rounded-xl opacity-0 group-hover:opacity-100 transition-opacity duration-300 pointer-events-none ${
|
||||||
isCompleted
|
isCompleted
|
||||||
? 'bg-gradient-to-r from-green-50 to-emerald-50 dark:from-green-900/10 dark:to-emerald-900/10'
|
? 'bg-gradient-to-r from-green-50 to-emerald-50 dark:from-green-900/20 dark:to-emerald-900/20'
|
||||||
: 'bg-gradient-to-r from-purple-50 to-pink-50 dark:from-purple-900/10 dark:to-pink-900/10'
|
: 'bg-gradient-to-r from-purple-50 to-pink-50 dark:from-purple-900/20 dark:to-pink-900/20'
|
||||||
}`}></div>
|
}`}></div>
|
||||||
|
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
|
|
@ -71,10 +71,10 @@ const Surveys: React.FC<SurveysProps> = ({ surveys, onTakeSurvey }) => {
|
||||||
<div
|
<div
|
||||||
className={`px-2 py-1 text-center rounded-full text-xs font-medium ${
|
className={`px-2 py-1 text-center rounded-full text-xs font-medium ${
|
||||||
urgency === 'urgent'
|
urgency === 'urgent'
|
||||||
? 'bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-300'
|
? 'bg-red-100 text-red-700 dark:bg-red-900/40 dark:text-red-300'
|
||||||
: urgency === 'warning'
|
: urgency === 'warning'
|
||||||
? 'bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-300'
|
? 'bg-amber-100 text-amber-700 dark:bg-amber-900/40 dark:text-amber-300'
|
||||||
: 'bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-300'
|
: 'bg-green-100 text-green-700 dark:bg-green-900/40 dark:text-green-300'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{daysLeft > 0 ? translate('::App.Platform.Intranet.Widgets.ActiveSurveys.DaysLeft', { count: daysLeft }) : translate('::App.Platform.Intranet.Widgets.ActiveSurveys.LastDay')}
|
{daysLeft > 0 ? translate('::App.Platform.Intranet.Widgets.ActiveSurveys.DaysLeft', { count: daysLeft }) : translate('::App.Platform.Intranet.Widgets.ActiveSurveys.LastDay')}
|
||||||
|
|
@ -84,8 +84,8 @@ const Surveys: React.FC<SurveysProps> = ({ surveys, onTakeSurvey }) => {
|
||||||
{/* Survey Stats */}
|
{/* Survey Stats */}
|
||||||
<div className="grid grid-cols-3 gap-4 mb-4">
|
<div className="grid grid-cols-3 gap-4 mb-4">
|
||||||
<div className="flex items-center gap-2 text-sm">
|
<div className="flex items-center gap-2 text-sm">
|
||||||
<div className="p-1.5 bg-blue-100 dark:bg-blue-900/30 rounded-lg">
|
<div className="p-1.5 bg-blue-100 dark:bg-blue-900/40 rounded-lg">
|
||||||
<FaQuestionCircle className="w-3 h-3 text-blue-600 dark:text-blue-400" />
|
<FaQuestionCircle className="w-3 h-3 text-blue-600 dark:text-blue-300" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-xs text-gray-500 dark:text-gray-400">{translate('::App.Platform.Intranet.Widgets.ActiveSurveys.Questions')}</p>
|
<p className="text-xs text-gray-500 dark:text-gray-400">{translate('::App.Platform.Intranet.Widgets.ActiveSurveys.Questions')}</p>
|
||||||
|
|
@ -96,8 +96,8 @@ const Surveys: React.FC<SurveysProps> = ({ surveys, onTakeSurvey }) => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center gap-2 text-sm">
|
<div className="flex items-center gap-2 text-sm">
|
||||||
<div className="p-1.5 bg-green-100 dark:bg-green-900/30 rounded-lg">
|
<div className="p-1.5 bg-green-100 dark:bg-green-900/40 rounded-lg">
|
||||||
<FaUsers className="w-3 h-3 text-green-600 dark:text-green-400" />
|
<FaUsers className="w-3 h-3 text-green-600 dark:text-green-300" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-xs text-gray-500 dark:text-gray-400">{translate('::App.Platform.Intranet.Widgets.ActiveSurveys.Responses')}</p>
|
<p className="text-xs text-gray-500 dark:text-gray-400">{translate('::App.Platform.Intranet.Widgets.ActiveSurveys.Responses')}</p>
|
||||||
|
|
@ -108,8 +108,8 @@ const Surveys: React.FC<SurveysProps> = ({ surveys, onTakeSurvey }) => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center gap-2 text-sm">
|
<div className="flex items-center gap-2 text-sm">
|
||||||
<div className="p-1.5 bg-purple-100 dark:bg-purple-900/30 rounded-lg">
|
<div className="p-1.5 bg-purple-100 dark:bg-purple-900/40 rounded-lg">
|
||||||
<FaClock className="w-3 h-3 text-purple-600 dark:text-purple-400" />
|
<FaClock className="w-3 h-3 text-purple-600 dark:text-purple-300" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-xs text-gray-500 dark:text-gray-400">{translate('::App.Platform.Intranet.Widgets.ActiveSurveys.Duration')}</p>
|
<p className="text-xs text-gray-500 dark:text-gray-400">{translate('::App.Platform.Intranet.Widgets.ActiveSurveys.Duration')}</p>
|
||||||
|
|
@ -122,13 +122,13 @@ const Surveys: React.FC<SurveysProps> = ({ surveys, onTakeSurvey }) => {
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<div className="flex justify-between text-xs mb-1">
|
<div className="flex justify-between text-xs mb-1">
|
||||||
<span className="text-gray-600 dark:text-gray-400">{translate('::App.Platform.Intranet.Widgets.ActiveSurveys.CompletionRate')}</span>
|
<span className="text-gray-600 dark:text-gray-400">{translate('::App.Platform.Intranet.Widgets.ActiveSurveys.CompletionRate')}</span>
|
||||||
<span className="text-gray-800 dark:text-gray-200 font-medium">
|
<span className="text-gray-800 dark:text-gray-100 font-medium">
|
||||||
{Math.round((survey.responses / 100) * 100)}%
|
{Math.round((survey.responses / 100) * 100)}%
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full bg-gray-200 dark:bg-gray-600 rounded-full h-2">
|
<div className="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-2">
|
||||||
<div
|
<div
|
||||||
className="bg-gradient-to-r from-purple-500 to-pink-500 h-2 rounded-full transition-all duration-500"
|
className="bg-gradient-to-r from-purple-500 to-pink-500 dark:from-purple-700 dark:to-pink-700 h-2 rounded-full transition-all duration-500"
|
||||||
style={{ width: `${Math.min((survey.responses / 100) * 100, 100)}%` }}
|
style={{ width: `${Math.min((survey.responses / 100) * 100, 100)}%` }}
|
||||||
></div>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -136,15 +136,15 @@ const Surveys: React.FC<SurveysProps> = ({ surveys, onTakeSurvey }) => {
|
||||||
|
|
||||||
{/* Deadline */}
|
{/* Deadline */}
|
||||||
<p className="text-sm text-gray-600 dark:text-gray-400 mb-4">
|
<p className="text-sm text-gray-600 dark:text-gray-400 mb-4">
|
||||||
<FaClock className="inline w-3 h-3 mr-1" />
|
<FaClock className="inline w-3 h-3 mr-1 text-purple-500 dark:text-purple-300" />
|
||||||
{currentLocalDate(survey.deadline, currentLocale || 'tr')}
|
{currentLocalDate(survey.deadline, currentLocale || 'tr')}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{/* Action Button */}
|
{/* Action Button */}
|
||||||
<button className={`w-full flex items-center justify-center gap-2 px-4 py-3 text-white text-sm font-medium rounded-lg transition-all duration-300 transform group-hover:scale-[1.02] shadow-sm hover:shadow-md ${
|
<button className={`w-full flex items-center justify-center gap-2 px-4 py-3 text-white text-sm font-medium rounded-lg transition-all duration-300 transform group-hover:scale-[1.02] shadow-sm hover:shadow-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-purple-500 dark:focus:ring-purple-400 ${
|
||||||
isCompleted
|
isCompleted
|
||||||
? 'bg-gradient-to-r from-green-500 to-emerald-500 hover:from-green-600 hover:to-emerald-600'
|
? 'bg-gradient-to-r from-green-500 to-emerald-500 hover:from-green-600 hover:to-emerald-600 dark:from-green-600 dark:to-emerald-600 dark:hover:from-green-700 dark:hover:to-emerald-700'
|
||||||
: 'bg-gradient-to-r from-purple-600 to-pink-600 hover:from-purple-700 hover:to-pink-700'
|
: 'bg-gradient-to-r from-purple-600 to-pink-600 hover:from-purple-700 hover:to-pink-700 dark:from-purple-700 dark:to-pink-700 dark:hover:from-purple-800 dark:hover:to-pink-800'
|
||||||
}`}>
|
}`}>
|
||||||
{isCompleted
|
{isCompleted
|
||||||
? translate('::App.Platform.Intranet.Widgets.ActiveSurveys.ViewResponses')
|
? translate('::App.Platform.Intranet.Widgets.ActiveSurveys.ViewResponses')
|
||||||
|
|
@ -157,9 +157,9 @@ const Surveys: React.FC<SurveysProps> = ({ surveys, onTakeSurvey }) => {
|
||||||
})}
|
})}
|
||||||
|
|
||||||
{surveys?.length === 0 && (
|
{surveys?.length === 0 && (
|
||||||
<div className="text-center py-12">
|
<div className="text-center py-12 bg-white dark:bg-gray-800 rounded-xl">
|
||||||
<div className="inline-flex items-center justify-center w-16 h-16 bg-gray-100 dark:bg-gray-700 rounded-full mb-4">
|
<div className="inline-flex items-center justify-center w-16 h-16 bg-gray-100 dark:bg-gray-700 rounded-full mb-4">
|
||||||
<FaClipboardCheck className="w-8 h-8 text-gray-400" />
|
<FaClipboardCheck className="w-8 h-8 text-gray-400 dark:text-gray-500" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="text-lg font-medium text-gray-900 dark:text-white mb-2">
|
<h3 className="text-lg font-medium text-gray-900 dark:text-white mb-2">
|
||||||
{translate('::App.Platform.Intranet.Widgets.ActiveSurveys.NoActive')}
|
{translate('::App.Platform.Intranet.Widgets.ActiveSurveys.NoActive')}
|
||||||
|
|
|
||||||
|
|
@ -140,30 +140,30 @@ export const MenuItemComponent: React.FC<MenuItemComponentProps> = ({
|
||||||
style={style}
|
style={style}
|
||||||
className={`
|
className={`
|
||||||
flex items-center gap-1 p-1 rounded-lg transition-all duration-200 group min-h-[30px]
|
flex items-center gap-1 p-1 rounded-lg transition-all duration-200 group min-h-[30px]
|
||||||
${isDesignMode ? 'cursor-move hover:bg-blue-50 border border-transparent hover:border-blue-200' : 'cursor-pointer hover:bg-gray-50'}
|
${isDesignMode ? 'cursor-move hover:bg-blue-50 dark:hover:bg-blue-900 border border-transparent hover:border-blue-200 dark:hover:border-blue-400' : 'cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800'}
|
||||||
${isDragOverlay ? 'shadow-lg bg-white border border-blue-300 z-50' : ''}
|
${isDragOverlay ? 'shadow-lg bg-white dark:bg-gray-800 border border-blue-300 dark:border-blue-500 z-50' : ''}
|
||||||
${isDragging ? 'opacity-50' : ''}
|
${isDragging ? 'opacity-50' : ''}
|
||||||
${item.children && item.children.length > 0 ? 'bg-blue-50' : depth === 0 ? 'bg-white' : 'bg-gray-50'}
|
${item.children && item.children.length > 0 ? 'bg-blue-50 dark:bg-blue-900' : depth === 0 ? 'bg-white dark:bg-gray-800' : 'bg-gray-50 dark:bg-gray-900'}
|
||||||
`}
|
`}
|
||||||
{...(isDesignMode ? { ...attributes, ...listeners } : { onClick: toggleExpanded })}
|
{...(isDesignMode ? { ...attributes, ...listeners } : { onClick: toggleExpanded })}
|
||||||
>
|
>
|
||||||
{isDesignMode && (
|
{isDesignMode && (
|
||||||
<div className="flex gap-2 items-center mr-2">
|
<div className="flex gap-2 items-center mr-2">
|
||||||
<button onClick={openCreateModal} title="New Item">
|
<button onClick={openCreateModal} title="New Item">
|
||||||
<FaPlus size={16} className="text-green-600 hover:text-green-800" />
|
<FaPlus size={16} className="text-green-600 hover:text-green-800 dark:text-green-400 dark:hover:text-green-300" />
|
||||||
</button>
|
</button>
|
||||||
<button onClick={handleDelete} title="Delete Item">
|
<button onClick={handleDelete} title="Delete Item">
|
||||||
<FaTrashAlt size={16} className="text-red-600 hover:text-red-800" />
|
<FaTrashAlt size={16} className="text-red-600 hover:text-red-800 dark:text-red-400 dark:hover:text-red-300" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="flex items-center gap-3 flex-1 min-w-0">
|
<div className="flex items-center gap-3 flex-1 min-w-0">
|
||||||
<div className="flex-shrink-0 text-gray-600 text-xl">
|
<div className="flex-shrink-0 text-gray-600 dark:text-gray-300 text-xl">
|
||||||
{navigationIcon[item.icon || ''] ? (
|
{navigationIcon[item.icon || ''] ? (
|
||||||
React.createElement(navigationIcon[item.icon || ''], { className: 'text-gray-400' })
|
React.createElement(navigationIcon[item.icon || ''], { className: 'text-gray-400 dark:text-gray-500' })
|
||||||
) : (
|
) : (
|
||||||
<FaQuestionCircle className="text-gray-400" />
|
<FaQuestionCircle className="text-gray-400 dark:text-gray-500" />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -171,26 +171,26 @@ export const MenuItemComponent: React.FC<MenuItemComponentProps> = ({
|
||||||
type="button"
|
type="button"
|
||||||
onClick={openEditModal}
|
onClick={openEditModal}
|
||||||
className={`
|
className={`
|
||||||
truncate text-gray-800 leading-6 text-sm text-left
|
truncate text-gray-800 dark:text-gray-100 leading-6 text-sm text-left
|
||||||
${item.children && item.children.length > 0 ? 'font-semibold' : 'font-normal'}
|
${item.children && item.children.length > 0 ? 'font-semibold' : 'font-normal'}
|
||||||
${isDesignMode ? 'hover:text-blue-600' : ''}
|
${isDesignMode ? 'hover:text-blue-600 dark:hover:text-blue-400' : ''}
|
||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
{translate('::' + item.displayName)}
|
{translate('::' + item.displayName)}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{item.url && <FaExternalLinkAlt size={12} className="flex-shrink-0 text-gray-400" />}
|
{item.url && <FaExternalLinkAlt size={12} className="flex-shrink-0 text-gray-400 dark:text-gray-500" />}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center gap-2 flex-shrink-0">
|
<div className="flex items-center gap-2 flex-shrink-0">
|
||||||
{isDesignMode && (
|
{isDesignMode && (
|
||||||
<div className="flex items-center gap-2 text-xs text-gray-500">
|
<div className="flex items-center gap-2 text-xs text-gray-500 dark:text-gray-400">
|
||||||
<span className="bg-gray-200 px-2 py-1 rounded">#{item.order}</span>
|
<span className="bg-gray-200 dark:bg-gray-700 px-2 py-1 rounded">#{item.order}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{item.children && item.children.length > 0 && (
|
{item.children && item.children.length > 0 && (
|
||||||
<span className="text-xs text-gray-500 bg-blue-100 px-2 py-1 rounded-full">
|
<span className="text-xs text-gray-500 dark:text-gray-300 bg-blue-100 dark:bg-blue-900 px-2 py-1 rounded-full">
|
||||||
{item.children.length}
|
{item.children.length}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
|
@ -206,7 +206,7 @@ export const MenuItemComponent: React.FC<MenuItemComponentProps> = ({
|
||||||
onRequestClose={() => setIsModalOpen(false)}
|
onRequestClose={() => setIsModalOpen(false)}
|
||||||
width={600}
|
width={600}
|
||||||
>
|
>
|
||||||
<h5 className="mb-4">
|
<h5 className="mb-4 dark:text-gray-100">
|
||||||
{modalMode === 'edit' ? translate('::Edit Menu Item') : translate('::New Item')}
|
{modalMode === 'edit' ? translate('::Edit Menu Item') : translate('::New Item')}
|
||||||
</h5>
|
</h5>
|
||||||
<Formik
|
<Formik
|
||||||
|
|
@ -249,7 +249,7 @@ export const MenuItemComponent: React.FC<MenuItemComponentProps> = ({
|
||||||
type="text"
|
type="text"
|
||||||
name="code"
|
name="code"
|
||||||
component={Input}
|
component={Input}
|
||||||
className="h-8 text-sm px-2"
|
className="h-8 text-sm px-2 dark:bg-gray-900 dark:text-gray-100"
|
||||||
autoFocus
|
autoFocus
|
||||||
/>
|
/>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
|
|
@ -259,7 +259,7 @@ export const MenuItemComponent: React.FC<MenuItemComponentProps> = ({
|
||||||
type="text"
|
type="text"
|
||||||
name="displayName"
|
name="displayName"
|
||||||
component={Input}
|
component={Input}
|
||||||
className="h-8 text-sm px-2"
|
className="h-8 text-sm px-2 dark:bg-gray-900 dark:text-gray-100"
|
||||||
/>
|
/>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
|
|
||||||
|
|
@ -268,23 +268,23 @@ export const MenuItemComponent: React.FC<MenuItemComponentProps> = ({
|
||||||
type="number"
|
type="number"
|
||||||
name="order"
|
name="order"
|
||||||
component={Input}
|
component={Input}
|
||||||
className="h-8 text-sm px-2"
|
className="h-8 text-sm px-2 dark:bg-gray-900 dark:text-gray-100"
|
||||||
/>
|
/>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
|
|
||||||
<FormItem label="URL" className="mb-2">
|
<FormItem label="URL" className="mb-2">
|
||||||
<Field type="text" name="url" component={Input} className="h-8 text-sm px-2" />
|
<Field type="text" name="url" component={Input} className="h-8 text-sm px-2 dark:bg-gray-900 dark:text-gray-100" />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
|
|
||||||
<FormItem label="Icon" className="mb-2">
|
<FormItem label="Icon" className="mb-2">
|
||||||
<Field type="text" name="icon" component={Input} className="h-8 text-sm px-2" />
|
<Field type="text" name="icon" component={Input} className="h-8 text-sm px-2 dark:bg-gray-900 dark:text-gray-100" />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
|
|
||||||
<FormItem label="Parent Code" className="mb-2">
|
<FormItem label="Parent Code" className="mb-2">
|
||||||
<Input
|
<Input
|
||||||
disabled
|
disabled
|
||||||
value={values.parentCode || ''}
|
value={values.parentCode || ''}
|
||||||
className="h-8 text-sm px-2 bg-gray-100"
|
className="h-8 text-sm px-2 bg-gray-100 dark:bg-gray-800 dark:text-gray-300"
|
||||||
/>
|
/>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
|
|
||||||
|
|
@ -293,7 +293,7 @@ export const MenuItemComponent: React.FC<MenuItemComponentProps> = ({
|
||||||
type="text"
|
type="text"
|
||||||
name="cssClass"
|
name="cssClass"
|
||||||
component={Input}
|
component={Input}
|
||||||
className="h-8 text-sm px-2"
|
className="h-8 text-sm px-2 dark:bg-gray-900 dark:text-gray-100"
|
||||||
/>
|
/>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
|
|
||||||
|
|
@ -302,7 +302,7 @@ export const MenuItemComponent: React.FC<MenuItemComponentProps> = ({
|
||||||
type="text"
|
type="text"
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
name="requiredPermissionName"
|
name="requiredPermissionName"
|
||||||
className="h-8 text-sm px-2"
|
className="h-8 text-sm px-2 dark:bg-gray-900 dark:text-gray-100"
|
||||||
>
|
>
|
||||||
{({ field, form }: FieldProps<SelectBoxOption>) => (
|
{({ field, form }: FieldProps<SelectBoxOption>) => (
|
||||||
<Select
|
<Select
|
||||||
|
|
@ -324,7 +324,7 @@ export const MenuItemComponent: React.FC<MenuItemComponentProps> = ({
|
||||||
type="text"
|
type="text"
|
||||||
name="target"
|
name="target"
|
||||||
component={Input}
|
component={Input}
|
||||||
className="h-8 text-sm px-2"
|
className="h-8 text-sm px-2 dark:bg-gray-900 dark:text-gray-100"
|
||||||
/>
|
/>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
|
|
||||||
|
|
@ -333,7 +333,7 @@ export const MenuItemComponent: React.FC<MenuItemComponentProps> = ({
|
||||||
type="text"
|
type="text"
|
||||||
name="elementId"
|
name="elementId"
|
||||||
component={Input}
|
component={Input}
|
||||||
className="h-8 text-sm px-2"
|
className="h-8 text-sm px-2 dark:bg-gray-900 dark:text-gray-100"
|
||||||
/>
|
/>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -52,8 +52,8 @@ export const MenuManager = () => {
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
|
<div className="min-h-screen bg-gray-50 dark:bg-gray-900 flex items-center justify-center">
|
||||||
<div className="flex items-center gap-3 text-gray-600">
|
<div className="flex items-center gap-3 text-gray-600 dark:text-gray-300">
|
||||||
<FaSpinner className="animate-spin" />
|
<FaSpinner className="animate-spin" />
|
||||||
<span className="text-lg">Loading menu configuration...</span>
|
<span className="text-lg">Loading menu configuration...</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -63,16 +63,16 @@ export const MenuManager = () => {
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
|
<div className="min-h-screen bg-gray-50 dark:bg-gray-900 flex items-center justify-center">
|
||||||
<div className="bg-white p-8 rounded-lg shadow-md max-w-md w-full mx-4">
|
<div className="bg-white dark:bg-gray-800 p-8 rounded-lg shadow-md max-w-md w-full mx-4">
|
||||||
<div className="flex items-center gap-3 text-red-600 mb-4">
|
<div className="flex items-center gap-3 text-red-600 dark:text-red-400 mb-4">
|
||||||
<FaRegBell size={24} />
|
<FaRegBell size={24} />
|
||||||
<h2 className="text-lg font-semibold">Error Loading Menu</h2>
|
<h2 className="text-lg font-semibold">Error Loading Menu</h2>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-gray-600 mb-6">{error}</p>
|
<p className="text-gray-600 dark:text-gray-300 mb-6">{error}</p>
|
||||||
<button
|
<button
|
||||||
onClick={refetch}
|
onClick={refetch}
|
||||||
className="w-full bg-blue-600 text-white py-2 px-4 rounded-lg hover:bg-blue-700 transition-colors"
|
className="w-full bg-blue-600 dark:bg-blue-700 text-white py-2 px-4 rounded-lg hover:bg-blue-700 dark:hover:bg-blue-800 transition-colors"
|
||||||
>
|
>
|
||||||
Retry
|
Retry
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -89,20 +89,20 @@ export const MenuManager = () => {
|
||||||
defaultTitle={APP_NAME}
|
defaultTitle={APP_NAME}
|
||||||
></Helmet>
|
></Helmet>
|
||||||
|
|
||||||
<div className="bg-white rounded px-2 sm:px-2 lg:px-3 py-3">
|
<div className="bg-white dark:bg-gray-800 rounded px-2 sm:px-2 lg:px-3 py-3">
|
||||||
<div className="flex items-center justify-between mb-2 flex-wrap gap-4">
|
<div className="flex items-center justify-between mb-2 flex-wrap gap-4">
|
||||||
{/* Sol kısım: Başlık */}
|
{/* Sol kısım: Başlık */}
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<FaBars size={20} className="text-gray-600" />
|
<FaBars size={20} className="text-gray-600 dark:text-gray-300" />
|
||||||
<h2 className="text-base font-semibold text-gray-900">Menu Manager</h2>
|
<h2 className="text-base font-semibold text-gray-900 dark:text-gray-100">Menu Manager</h2>
|
||||||
<span className="text-sm text-gray-500">({menuItems.length} root items)</span>
|
<span className="text-sm text-gray-500 dark:text-gray-400">({menuItems.length} root items)</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Sağ kısım: Design Mode + Save butonu */}
|
{/* Sağ kısım: Design Mode + Save butonu */}
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<span
|
<span
|
||||||
className={`text-sm font-medium ${isDesignMode ? 'text-blue-600' : 'text-gray-500'}`}
|
className={`text-sm font-medium ${isDesignMode ? 'text-blue-600 dark:text-blue-400' : 'text-gray-500 dark:text-gray-400'}`}
|
||||||
>
|
>
|
||||||
Design Mode
|
Design Mode
|
||||||
</span>
|
</span>
|
||||||
|
|
@ -110,12 +110,12 @@ export const MenuManager = () => {
|
||||||
onClick={handleToggleDesignMode}
|
onClick={handleToggleDesignMode}
|
||||||
className={`
|
className={`
|
||||||
relative inline-flex h-6 w-11 items-center rounded-full transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2
|
relative inline-flex h-6 w-11 items-center rounded-full transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2
|
||||||
${isDesignMode ? 'bg-blue-600' : 'bg-gray-200'}
|
${isDesignMode ? 'bg-blue-600 dark:bg-blue-700' : 'bg-gray-200 dark:bg-gray-700'}
|
||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
className={`
|
className={`
|
||||||
inline-block h-4 w-4 transform rounded-full bg-white transition-transform duration-200 ease-in-out
|
inline-block h-4 w-4 transform rounded-full bg-white dark:bg-gray-200 transition-transform duration-200 ease-in-out
|
||||||
${isDesignMode ? 'translate-x-6' : 'translate-x-1'}
|
${isDesignMode ? 'translate-x-6' : 'translate-x-1'}
|
||||||
`}
|
`}
|
||||||
/>
|
/>
|
||||||
|
|
@ -128,7 +128,7 @@ export const MenuManager = () => {
|
||||||
disabled={!isDesignMode || isSaving}
|
disabled={!isDesignMode || isSaving}
|
||||||
className={`
|
className={`
|
||||||
flex items-center gap-2 px-2 py-1 rounded-lg transition-colors
|
flex items-center gap-2 px-2 py-1 rounded-lg transition-colors
|
||||||
${isDesignMode ? 'bg-green-600 hover:bg-green-700 text-white' : 'bg-gray-300 text-gray-500 cursor-not-allowed'}
|
${isDesignMode ? 'bg-green-600 dark:bg-green-700 hover:bg-green-700 dark:hover:bg-green-800 text-white' : 'bg-gray-300 dark:bg-gray-700 text-gray-500 dark:text-gray-400 cursor-not-allowed'}
|
||||||
${isSaving ? 'opacity-50' : ''}
|
${isSaving ? 'opacity-50' : ''}
|
||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
|
|
@ -156,8 +156,8 @@ export const MenuManager = () => {
|
||||||
refetch={refetch}
|
refetch={refetch}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div className="text-center py-12 text-gray-500">
|
<div className="text-center py-12 text-gray-500 dark:text-gray-400">
|
||||||
<FaBars size={24} className="mx-auto mb-4 text-gray-300" />
|
<FaBars size={24} className="mx-auto mb-4 text-gray-300 dark:text-gray-600" />
|
||||||
<p className="text-lg">No menu items found</p>
|
<p className="text-lg">No menu items found</p>
|
||||||
<p className="text-sm">Try refreshing the page or contact your administrator</p>
|
<p className="text-sm">Try refreshing the page or contact your administrator</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -192,7 +192,7 @@ export const SortableMenuTree: React.FC<SortableMenuTreeProps> = ({
|
||||||
|
|
||||||
const renderMenuItem = (item: MenuItem, depth: number = 0): React.ReactNode => {
|
const renderMenuItem = (item: MenuItem, depth: number = 0): React.ReactNode => {
|
||||||
return (
|
return (
|
||||||
<div key={item.id}>
|
<div key={item.id} className="bg-white dark:bg-gray-800 rounded-md">
|
||||||
<MenuItemComponent item={item} isDesignMode={isDesignMode} depth={depth} refetch={refetch} permissions={permissions}>
|
<MenuItemComponent item={item} isDesignMode={isDesignMode} depth={depth} refetch={refetch} permissions={permissions}>
|
||||||
{Array.isArray(item.children) && item.children.length > 0 && (
|
{Array.isArray(item.children) && item.children.length > 0 && (
|
||||||
<SortableContext
|
<SortableContext
|
||||||
|
|
@ -201,7 +201,7 @@ export const SortableMenuTree: React.FC<SortableMenuTreeProps> = ({
|
||||||
.map((child) => child.id)}
|
.map((child) => child.id)}
|
||||||
strategy={verticalListSortingStrategy}
|
strategy={verticalListSortingStrategy}
|
||||||
>
|
>
|
||||||
<div className="ml-4">
|
<div className="ml-4 border-gray-200 dark:border-gray-700">
|
||||||
{item.children.map((child) => renderMenuItem(child, depth + 1))}
|
{item.children.map((child) => renderMenuItem(child, depth + 1))}
|
||||||
</div>
|
</div>
|
||||||
</SortableContext>
|
</SortableContext>
|
||||||
|
|
@ -222,14 +222,16 @@ export const SortableMenuTree: React.FC<SortableMenuTreeProps> = ({
|
||||||
|
|
||||||
<DragOverlay>
|
<DragOverlay>
|
||||||
{activeItem ? (
|
{activeItem ? (
|
||||||
<MenuItemComponent
|
<div className="bg-white dark:bg-gray-800 rounded-md shadow-lg">
|
||||||
item={activeItem}
|
<MenuItemComponent
|
||||||
isDesignMode={isDesignMode}
|
item={activeItem}
|
||||||
depth={0}
|
isDesignMode={isDesignMode}
|
||||||
isDragOverlay={true}
|
depth={0}
|
||||||
refetch={refetch}
|
isDragOverlay={true}
|
||||||
permissions={permissions}
|
refetch={refetch}
|
||||||
/>
|
permissions={permissions}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
</DragOverlay>
|
</DragOverlay>
|
||||||
</DndContext>
|
</DndContext>
|
||||||
|
|
|
||||||
|
|
@ -50,18 +50,18 @@ const DesignerDrawer: React.FC<DesignerDrawerProps> = ({
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed inset-y-0 right-0 z-50 w-[420px] border-l border-slate-200 bg-white shadow-2xl">
|
<div className="fixed inset-y-0 right-0 z-50 w-[420px] border-l border-slate-200 dark:border-gray-700 bg-white dark:bg-gray-900 shadow-2xl">
|
||||||
<div className="flex h-full flex-col bg-white">
|
<div className="flex h-full flex-col bg-white dark:bg-gray-900">
|
||||||
<div className="border-b border-slate-200 px-5 py-4">
|
<div className="border-b border-slate-200 dark:border-gray-700 px-5 py-4">
|
||||||
<div className="flex items-start justify-between gap-4">
|
<div className="flex items-start justify-between gap-4">
|
||||||
<div>
|
<div>
|
||||||
<p className="text-xs font-semibold uppercase tracking-[0.18em] text-sky-700">
|
<p className="text-xs font-semibold uppercase tracking-[0.18em] text-sky-700 dark:text-sky-400">
|
||||||
{pageTitle} {translate('::Public.designer.title')}
|
{pageTitle} {translate('::Public.designer.title')}
|
||||||
</p>
|
</p>
|
||||||
<h3 className="mt-2 text-lg font-semibold text-slate-900">
|
<h3 className="mt-2 text-lg font-semibold text-slate-900 dark:text-gray-100">
|
||||||
{selection?.title ?? translate('::Public.designer.noSelection')}
|
{selection?.title ?? translate('::Public.designer.noSelection')}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="mt-1 text-sm text-slate-500">
|
<p className="mt-1 text-sm text-slate-500 dark:text-gray-400">
|
||||||
{selection?.description ?? translate('::Public.designer.selectField')}
|
{selection?.description ?? translate('::Public.designer.selectField')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -80,8 +80,8 @@ const DesignerDrawer: React.FC<DesignerDrawerProps> = ({
|
||||||
className={`flex h-9 w-9 items-center justify-center rounded-lg text-xl transition-all
|
className={`flex h-9 w-9 items-center justify-center rounded-lg text-xl transition-all
|
||||||
${
|
${
|
||||||
normalizeLanguageKey(selectedLanguage) === language.key
|
normalizeLanguageKey(selectedLanguage) === language.key
|
||||||
? 'border-sky-500 bg-sky-50 ring-2 ring-sky-200'
|
? 'border-sky-500 bg-sky-50 dark:bg-sky-900/20 ring-2 ring-sky-200 dark:ring-sky-700'
|
||||||
: 'border-slate-200 hover:border-slate-400 hover:bg-slate-50'
|
: 'border-slate-200 dark:border-gray-700 hover:border-slate-400 dark:hover:border-gray-500 hover:bg-slate-50 dark:hover:bg-gray-800'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<Avatar
|
<Avatar
|
||||||
|
|
@ -114,11 +114,11 @@ const DesignerDrawer: React.FC<DesignerDrawerProps> = ({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={field.key}>
|
<div key={field.key}>
|
||||||
<label className="mb-1 block text-sm font-semibold text-slate-700">
|
<label className="mb-1 block text-sm font-semibold text-slate-700 dark:text-gray-200">
|
||||||
{field.localizationKey || field.label}
|
{field.localizationKey || field.label}
|
||||||
</label>
|
</label>
|
||||||
{field.localizationKey && field.localizationKey !== field.label && (
|
{field.localizationKey && field.localizationKey !== field.label && (
|
||||||
<p className="mb-2 text-xs text-slate-500">{field.label}</p>
|
<p className="mb-2 text-xs text-slate-500 dark:text-gray-400">{field.label}</p>
|
||||||
)}
|
)}
|
||||||
{field.type === 'icon' ? (
|
{field.type === 'icon' ? (
|
||||||
<IconPickerField
|
<IconPickerField
|
||||||
|
|
@ -137,13 +137,13 @@ const DesignerDrawer: React.FC<DesignerDrawerProps> = ({
|
||||||
})}
|
})}
|
||||||
|
|
||||||
{!selection && (
|
{!selection && (
|
||||||
<div className="rounded-2xl border border-dashed border-slate-300 bg-slate-50 px-4 py-6 text-sm text-slate-500">
|
<div className="rounded-2xl border border-dashed border-slate-300 dark:border-gray-700 bg-slate-50 dark:bg-gray-800 px-4 py-6 text-sm text-slate-500 dark:text-gray-400">
|
||||||
{translate('::Public.designer.noSelectionDetails')}
|
{translate('::Public.designer.noSelectionDetails')}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center justify-between border-t border-slate-200 px-5 py-4">
|
<div className="flex items-center justify-between border-t border-slate-200 dark:border-gray-700 px-5 py-4">
|
||||||
<Button variant="solid" block={true} onClick={onSave}>
|
<Button variant="solid" block={true} onClick={onSave}>
|
||||||
{translate('::Public.designer.save')}
|
{translate('::Public.designer.save')}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue