feat: Implement internationalization for task management components
- Added translation support for task-related strings in ProjectTaskForm and ProjectTasksSection components. - Integrated translation for navigation items in the Navigation component. - Created ProjectCalendarWidget component with Polish translations for project statuses and deadlines. - Developed Tooltip component for enhanced user experience with tooltips. - Established a field change history logging system in the database with associated queries. - Enhanced task update logging to include translated status and priority changes. - Introduced server-side translations for system messages to improve localization.
This commit is contained in:
95
src/components/ui/Tooltip.js
Normal file
95
src/components/ui/Tooltip.js
Normal file
@@ -0,0 +1,95 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useRef, useEffect } from "react";
|
||||
import { createPortal } from "react-dom";
|
||||
|
||||
export default function Tooltip({ children, content, className = "" }) {
|
||||
const [isVisible, setIsVisible] = useState(false);
|
||||
const [position, setPosition] = useState({ top: 0, left: 0 });
|
||||
const triggerRef = useRef(null);
|
||||
const tooltipRef = useRef(null);
|
||||
|
||||
const updatePosition = () => {
|
||||
if (triggerRef.current && tooltipRef.current) {
|
||||
const triggerRect = triggerRef.current.getBoundingClientRect();
|
||||
const tooltipRect = tooltipRef.current.getBoundingClientRect();
|
||||
const scrollY = window.scrollY;
|
||||
const scrollX = window.scrollX;
|
||||
|
||||
// Calculate position (above the trigger by default)
|
||||
let top = triggerRect.top + scrollY - tooltipRect.height - 8;
|
||||
let left = triggerRect.left + scrollX + (triggerRect.width / 2) - (tooltipRect.width / 2);
|
||||
|
||||
// Keep tooltip within viewport
|
||||
if (left < 10) left = 10;
|
||||
if (left + tooltipRect.width > window.innerWidth - 10) {
|
||||
left = window.innerWidth - tooltipRect.width - 10;
|
||||
}
|
||||
|
||||
// If tooltip would go above viewport, show below instead
|
||||
if (top < scrollY + 10) {
|
||||
top = triggerRect.bottom + scrollY + 8;
|
||||
}
|
||||
|
||||
setPosition({ top, left });
|
||||
}
|
||||
};
|
||||
|
||||
const handleMouseEnter = () => {
|
||||
setIsVisible(true);
|
||||
};
|
||||
|
||||
const handleMouseLeave = () => {
|
||||
setIsVisible(false);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (isVisible) {
|
||||
// Small delay to ensure tooltip is rendered before positioning
|
||||
const timer = setTimeout(() => {
|
||||
updatePosition();
|
||||
}, 10);
|
||||
|
||||
const handleScroll = () => updatePosition();
|
||||
const handleResize = () => updatePosition();
|
||||
|
||||
window.addEventListener("scroll", handleScroll, true);
|
||||
window.addEventListener("resize", handleResize);
|
||||
|
||||
return () => {
|
||||
clearTimeout(timer);
|
||||
window.removeEventListener("scroll", handleScroll, true);
|
||||
window.removeEventListener("resize", handleResize);
|
||||
};
|
||||
}
|
||||
}, [isVisible]);
|
||||
|
||||
const tooltip = isVisible && (
|
||||
<div
|
||||
ref={tooltipRef}
|
||||
className={`fixed z-50 px-3 py-2 text-sm bg-gray-900 text-white rounded-lg shadow-lg border max-w-sm ${className}`}
|
||||
style={{
|
||||
top: position.top,
|
||||
left: position.left,
|
||||
}}
|
||||
>
|
||||
{content}
|
||||
{/* Arrow pointing down */}
|
||||
<div className="absolute top-full left-1/2 transform -translate-x-1/2 border-4 border-transparent border-t-gray-900"></div>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<span
|
||||
ref={triggerRef}
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
className="inline-block"
|
||||
>
|
||||
{children}
|
||||
</span>
|
||||
{typeof document !== "undefined" && createPortal(tooltip, document.body)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user