From 871ee345364913a13543679c58573e52e0516081 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sedat=20=C3=96zt=C3=BCrk?= Date: Fri, 8 May 2026 23:29:06 +0300 Subject: [PATCH] =?UTF-8?q?Genel=20d=C3=BCzeltmeler=20Responsive=20ve=20Se?= =?UTF-8?q?eder?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Seeds/LanguagesData.json | 12 +- .../Seeds/ListFormSeeder_Administration.cs | 12 +- ui/src/components/ui/Widget/Widget.tsx | 2 +- ui/src/components/ui/Widget/WidgetGroup.tsx | 17 ++- ui/src/views/admin/hr/OrgChart.tsx | 46 ++++---- .../listForm/wizard/WizardFileManager.tsx | 24 ++-- .../intranet/widgets/AnnouncementModal.tsx | 110 +++++++++++++++--- ui/src/views/list/useListFormColumns.ts | 101 +++++++++++++--- ui/src/views/shared/MenuAddDialog.tsx | 2 +- 9 files changed, 248 insertions(+), 78 deletions(-) diff --git a/api/src/Sozsoft.Platform.DbMigrator/Seeds/LanguagesData.json b/api/src/Sozsoft.Platform.DbMigrator/Seeds/LanguagesData.json index 7a39b15..0c1b25d 100644 --- a/api/src/Sozsoft.Platform.DbMigrator/Seeds/LanguagesData.json +++ b/api/src/Sozsoft.Platform.DbMigrator/Seeds/LanguagesData.json @@ -16733,8 +16733,8 @@ { "resourceName": "Platform", "key": "ListForms.Wizard.MenuInfo", - "en": "Menu Information", - "tr": "Menü Bilgileri" + "en": "Menu", + "tr": "Menü" }, { "resourceName": "Platform", @@ -16751,14 +16751,14 @@ { "resourceName": "Platform", "key": "ListForms.Wizard.ListFormSettings", - "en": "List Form Settings", - "tr": "Liste Formu Ayarları" + "en": "Settings", + "tr": "Ayarlar" }, { "resourceName": "Platform", "key": "ListForms.Wizard.ListFormFields", - "en": "List Form Fields", - "tr": "Liste Formu Alanları" + "en": "Fields", + "tr": "Alanlar" }, { "resourceName": "Platform", diff --git a/api/src/Sozsoft.Platform.DbMigrator/Seeds/ListFormSeeder_Administration.cs b/api/src/Sozsoft.Platform.DbMigrator/Seeds/ListFormSeeder_Administration.cs index 2296068..e839a47 100644 --- a/api/src/Sozsoft.Platform.DbMigrator/Seeds/ListFormSeeder_Administration.cs +++ b/api/src/Sozsoft.Platform.DbMigrator/Seeds/ListFormSeeder_Administration.cs @@ -840,10 +840,10 @@ public class ListFormSeeder_Administration : IDataSeedContributor, ITransientDep ColSpan = 4, SqlQuery = @" SELECT - 'Aktif' AS ""Title"", + N'Aktif' AS ""Title"", COUNT(""Id"") AS ""Value"", 'blue' AS ""Color"", - 'Aktif Kullanıcılar' AS ""SubTitle"", + N'Aktif Kullanıcılar' AS ""SubTitle"", 'FaUserCheck' AS ""Icon"" FROM ""AbpUsers"" WHERE ""IsActive"" = 'true' @@ -851,10 +851,10 @@ public class ListFormSeeder_Administration : IDataSeedContributor, ITransientDep UNION ALL SELECT - 'Pasif' AS ""Title"", + N'Pasif' AS ""Title"", COUNT(""Id"") AS ""Value"", 'green' AS ""Color"", - 'Pasif Kullanıcılar' AS ""SubTitle"", + N'Pasif Kullanıcılar' AS ""SubTitle"", 'FaUserSlash' AS ""Icon"" FROM ""AbpUsers"" WHERE ""IsActive"" = 'false' @@ -862,10 +862,10 @@ public class ListFormSeeder_Administration : IDataSeedContributor, ITransientDep UNION ALL SELECT - 'Doğrulama' AS ""Title"", + N'Doğrulama' AS ""Title"", COUNT(""Id"") AS ""Value"", 'purple' AS ""Color"", - 'Yönetici Doğrulaması bekleyenler' AS ""SubTitle"", + N'Doğrulama bekleyenler' AS ""SubTitle"", 'FaUserClock' AS ""Icon"" FROM ""AbpUsers"" WHERE ""IsVerified"" = 'false'; diff --git a/ui/src/components/ui/Widget/Widget.tsx b/ui/src/components/ui/Widget/Widget.tsx index d9d18ef..9db29fb 100644 --- a/ui/src/components/ui/Widget/Widget.tsx +++ b/ui/src/components/ui/Widget/Widget.tsx @@ -77,7 +77,7 @@ export default function Widget({ >
-

{title}

+

{title}

{value}

{subTitle}

diff --git a/ui/src/components/ui/Widget/WidgetGroup.tsx b/ui/src/components/ui/Widget/WidgetGroup.tsx index 9dccfb2..dabba55 100644 --- a/ui/src/components/ui/Widget/WidgetGroup.tsx +++ b/ui/src/components/ui/Widget/WidgetGroup.tsx @@ -8,13 +8,25 @@ export default function WidgetGroup({ widgetGroups }: { widgetGroups: WidgetGrou return (
+ {/* Mobile responsive override: below sm breakpoint each widget spans 6/12 (2 per row) */} + + {widgetGroups.map((group, gIdx) => (
{group.items.map((item: WidgetEditDto, order: number) => ( -
+
) } + diff --git a/ui/src/views/admin/hr/OrgChart.tsx b/ui/src/views/admin/hr/OrgChart.tsx index 44a13cf..08286e6 100644 --- a/ui/src/views/admin/hr/OrgChart.tsx +++ b/ui/src/views/admin/hr/OrgChart.tsx @@ -452,7 +452,7 @@ function OrgChartNode({ data-card="" data-depth={depth} data-users-visible={showUsers ? 'true' : 'false'} - className={`relative bg-white border ${borderColor} rounded-xl shadow-sm w-52 hover:shadow-md transition-shadow`} + className={`relative bg-white border ${borderColor} rounded-xl shadow-sm w-36 sm:w-44 md:w-52 hover:shadow-md transition-shadow`} style={{ cursor: dragging ? 'grabbing' : 'grab' }} > {/* Header bar */} @@ -522,7 +522,7 @@ function OrgChartNode({ {/* Children */} {hasChildren && !collapsed && ( -
+
{node.children.map((child, idx) => { const childPosition = positions[child.id] ?? { x: 0, y: 0 } @@ -695,7 +695,7 @@ function OrgChartTree({ } return ( -
+
{paths.map((path, index) => ( -
+
{nodes.map((root) => ( {
{/* Toolbar */} -
-
+
+
{MenuIcon} -

+

{translate('::App.Definitions.OrgChart') || 'Organizasyon Şeması'}

-
+
-
- + {Math.round(zoom * 100)}%
diff --git a/ui/src/views/admin/listForm/wizard/WizardFileManager.tsx b/ui/src/views/admin/listForm/wizard/WizardFileManager.tsx index 8b5e213..1747201 100644 --- a/ui/src/views/admin/listForm/wizard/WizardFileManager.tsx +++ b/ui/src/views/admin/listForm/wizard/WizardFileManager.tsx @@ -109,21 +109,23 @@ const WizardFileManager = () => { {/* ── Header ─────────────────────────────────────────────── */}
- {MenuIcon} -

- {translate('::App.Listforms.WizardManager') || 'Wizard Seed Dosyaları'} -

+
+ {MenuIcon} +

+ {translate('::App.Listforms.WizardManager') || 'Wizard Seed Dosyaları'} +

+
-
+
setSearch(e.target.value)} @@ -145,7 +147,7 @@ const WizardFileManager = () => { onClick={() => setShowDbMigrateDialog(true)} title={translate('::App.DbMigrate.StartMessage') || 'Run DB Migration'} > - {translate('::ListForms.ListForm.DbMigrate') || 'DB Migrate'} + {translate('::ListForms.ListForm.DbMigrate') || 'DB Migrate'}
@@ -181,7 +183,7 @@ const WizardFileManager = () => { return (
@@ -197,7 +199,7 @@ const WizardFileManager = () => {
-
+
{!f.hasInsertedRecords && ( = ({ announcement, onClose }) => { const { translate } = useLocalization() const currentLocale = useLocale() + const [lightboxOpen, setLightboxOpen] = useState(false) + const [lightboxIndex, setLightboxIndex] = useState(0) + + const openLightbox = (idx: number) => { + setLightboxIndex(idx) + setLightboxOpen(true) + } + const closeLightbox = () => setLightboxOpen(false) + const prevLightbox = (e: React.MouseEvent) => { + e.stopPropagation() + setLightboxIndex((i) => (i - 1 + images.length) % images.length) + } + const nextLightbox = (e: React.MouseEvent) => { + e.stopPropagation() + setLightboxIndex((i) => (i + 1) % images.length) + } useEffect(() => { intranetService.incrementAnnouncementViewCount(announcement.id) }, [announcement.id]) + const images = announcement.imageUrl ? announcement.imageUrl.split('|').filter(Boolean) : [] + + const imgSrc = (img: string) => + img.startsWith('data:') || img.startsWith('http://') || img.startsWith('https://') || img.startsWith('/') + ? img + : `data:image/jpeg;base64,${img}` + const getCategoryColor = (category: string) => { const colors: Record = { general: 'bg-blue-100 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300', @@ -114,10 +137,8 @@ const AnnouncementModal: React.FC = ({ announcement, onC {/* Content */}
{/* Images if exist */} - {announcement.imageUrl && + {images.length > 0 && (() => { - const images = announcement.imageUrl.split('|').filter(Boolean) - if (images.length === 0) return null const getGridClass = (count: number) => { if (count === 1) return '' if (count === 2) return 'grid grid-cols-2 gap-2' @@ -135,16 +156,10 @@ const AnnouncementModal: React.FC = ({ announcement, onC {images.map((img, idx) => ( {`${announcement.title} 1 ? idx + 1 : ''}`.trim()} - className={getImgClass(images.length, idx)} + className={`${getImgClass(images.length, idx)} cursor-zoom-in hover:opacity-90 transition-opacity`} + onClick={() => openLightbox(idx)} /> ))}
@@ -236,6 +251,71 @@ const AnnouncementModal: React.FC = ({ announcement, onC
+ + {/* Lightbox */} + + {lightboxOpen && ( + <> + +
+ + {images.length > 1 && ( + <> + + + + )} + e.stopPropagation()} + /> + {images.length > 1 && ( +
+ {images.map((_, idx) => ( +
+ )} +
+ + )} +
) } diff --git a/ui/src/views/list/useListFormColumns.ts b/ui/src/views/list/useListFormColumns.ts index 1b25cbe..028adce 100644 --- a/ui/src/views/list/useListFormColumns.ts +++ b/ui/src/views/list/useListFormColumns.ts @@ -65,6 +65,73 @@ const cellTemplateMultiValue = ( } } +// Hover preview overlay — singleton, tüm grid hücreleri tarafından paylaşılır +let __imgPreviewEl: HTMLDivElement | null = null + +function getImgPreview(): HTMLDivElement { + if (!__imgPreviewEl) { + const el = document.createElement('div') + el.id = '__cellImgPreview' + el.style.cssText = [ + 'position:fixed', + 'z-index:99999', + 'display:none', + 'pointer-events:none', + 'background:#fff', + 'border:1px solid #d1d5db', + 'border-radius:8px', + 'box-shadow:0 8px 32px rgba(0,0,0,0.22)', + 'padding:4px', + 'max-width:320px', + 'max-height:320px', + 'overflow:hidden', + 'transition:opacity 0.15s ease', + 'opacity:0', + ].join(';') + const img = document.createElement('img') + img.style.cssText = + 'display:block;max-width:312px;max-height:312px;object-fit:contain;border-radius:4px;' + el.appendChild(img) + document.body.appendChild(el) + __imgPreviewEl = el + } + return __imgPreviewEl +} + +function showImgPreview(src: string, e: MouseEvent) { + const el = getImgPreview() + const imgEl = el.querySelector('img') as HTMLImageElement + if (imgEl.src !== src) imgEl.src = src + + const GAP = 12 + const vw = window.innerWidth + const vh = window.innerHeight + + el.style.opacity = '0' + el.style.display = 'block' + + const pw = el.offsetWidth || 320 + const ph = el.offsetHeight || 320 + let left = e.clientX + GAP + let top = e.clientY + GAP + + if (left + pw > vw - 8) left = e.clientX - pw - GAP + if (top + ph > vh - 8) top = e.clientY - ph - GAP + if (left < 8) left = 8 + if (top < 8) top = 8 + + el.style.left = `${left}px` + el.style.top = `${top}px` + el.style.opacity = '1' +} + +function hideImgPreview() { + if (__imgPreviewEl) { + __imgPreviewEl.style.opacity = '0' + __imgPreviewEl.style.display = 'none' + } +} + const cellTemplateImage = ( cellElement: HTMLElement, cellInfo: DataGridTypes.ColumnCellTemplateData, @@ -74,21 +141,29 @@ const cellTemplateImage = ( ? cellInfo.value.filter(Boolean) : [cellInfo.value].filter(Boolean) - const col = cellInfo.column as any - const imgOptions = col?.extras?.imageUploadOptions ?? {} - const w: number = imgOptions.width ?? 40 - const h: number = imgOptions.height ?? 40 - - const imgs = urls - .map( - (url) => - ``, - ) - .join('') + //const col = cellInfo.column as any + //const imgOptions = col?.extras?.imageUploadOptions ?? {} + //const w: number = imgOptions.width ?? 40 + //const h: number = imgOptions.height ?? 40 + const w: number = 40 + const h: number = 40 cellElement.style.cssText += 'display:flex;flex-wrap:wrap;align-items:center;gap:4px;' - cellElement.innerHTML = imgs - cellElement.title = urls.join(', ') + cellElement.innerHTML = '' + //cellElement.title = urls.join(', ') + + urls.forEach((url) => { + const img = document.createElement('img') + img.src = url + img.alt = '' + img.style.cssText = `width:${w}px;height:${h}px;object-fit:cover;border-radius:4px;border:1px solid #ddd;margin:2px;vertical-align:middle;display:inline-block;cursor:zoom-in;` + + img.addEventListener('mouseenter', (e) => showImgPreview(url, e as MouseEvent)) + img.addEventListener('mousemove', (e) => showImgPreview(url, e as MouseEvent)) + img.addEventListener('mouseleave', hideImgPreview) + + cellElement.appendChild(img) + }) } } diff --git a/ui/src/views/shared/MenuAddDialog.tsx b/ui/src/views/shared/MenuAddDialog.tsx index e3d146b..439364f 100644 --- a/ui/src/views/shared/MenuAddDialog.tsx +++ b/ui/src/views/shared/MenuAddDialog.tsx @@ -211,7 +211,7 @@ export function MenuAddDialog({ return ( -
+
{/* Header */}