erp-platform/ui/src/views/form/notes/NotePanel.tsx

231 lines
7.6 KiB
TypeScript
Raw Normal View History

import React, { useState, useRef, useEffect } from 'react'
import { NoteModal } from './NoteModal'
import { NoteList } from './NoteList'
import { Button, Badge } from '@/components/ui'
import {
FaChevronLeft,
FaChevronRight,
FaPlus,
FaTimes,
FaGripVertical,
FaChevronUp,
FaChevronDown,
} from 'react-icons/fa'
import { noteService } from '@/services/note.service'
import { NoteDto } from '@/proxy/note/models'
interface NotePanelProps {
entityName: string
entityId: string
isVisible: boolean
onToggle: () => void
}
export const NotePanel: React.FC<NotePanelProps> = ({
entityName,
entityId,
isVisible,
onToggle,
}) => {
const [showAddModal, setShowAddModal] = useState(false)
const [activities, setActivities] = useState<NoteDto[]>([])
const [buttonPosition, setButtonPosition] = useState({ top: '75%' })
const [isDragging, setIsDragging] = useState(false)
const [dragStart, setDragStart] = useState({ y: 0, startTop: 0 })
const buttonRef = useRef<HTMLDivElement>(null)
const [showEntityInfo, setShowEntityInfo] = useState(false)
// Fetch activities
const fetchActivities = async () => {
try {
const res = await noteService.getList({ entityName, entityId })
if (res?.items) setActivities(res.items)
} catch (err) {
console.error(err)
2025-10-13 21:47:53 +00:00
}
}
2025-10-13 21:47:53 +00:00
useEffect(() => {
if (isVisible) fetchActivities()
2025-10-13 21:47:53 +00:00
}, [isVisible])
const handleDownloadFile = async (fileData: any) => {
if (!fileData?.SavedFileName) return
try {
await noteService.downloadFile(
fileData.SavedFileName,
fileData.FileName,
fileData.FileType,
)
} catch (err) {
console.error('Dosya indirilemedi', err)
}
}
const handleDeleteActivity = async (activityId: string) => {
if (!confirm('Bu aktiviteyi silmek istediğinize emin misiniz?')) return
try {
await noteService.delete(activityId)
setActivities((prev) => prev.filter((a) => a.id !== activityId))
} catch (err) {
console.error(err)
}
}
const getTotalCount = () => activities.length
// Draggable button handlers
const handleMouseDown = (e: React.MouseEvent) => {
if (!buttonRef.current) return
e.preventDefault()
setIsDragging(true)
setDragStart({ y: e.clientY, startTop: buttonRef.current.getBoundingClientRect().top })
}
const handleMouseMove = (e: MouseEvent) => {
if (!isDragging || !buttonRef.current) return
e.preventDefault()
const deltaY = e.clientY - dragStart.y
const newTop = dragStart.startTop + deltaY
const viewportHeight = window.innerHeight
const buttonHeight = buttonRef.current.offsetHeight
const constrainedTop = Math.max(0, Math.min(viewportHeight - buttonHeight, newTop))
const topPercentage = (constrainedTop / viewportHeight) * 100
setButtonPosition({ top: `${topPercentage}%` })
}
const handleMouseUp = () => setIsDragging(false)
useEffect(() => {
if (isDragging) {
document.body.style.userSelect = 'none'
document.addEventListener('mousemove', handleMouseMove, { passive: false })
document.addEventListener('mouseup', handleMouseUp)
return () => {
document.body.style.userSelect = ''
document.removeEventListener('mousemove', handleMouseMove)
document.removeEventListener('mouseup', handleMouseUp)
}
}
}, [isDragging, dragStart])
if (!entityName || !entityId) return null
2025-10-13 21:47:53 +00:00
return (
<>
{/* Draggable toggle button */}
<div
ref={buttonRef}
className="fixed right-0 z-40"
style={{ top: buttonPosition.top, transform: 'translateY(-50%)' }}
>
<div className="group relative">
<div
className={`absolute -left-2 top-1/2 transform -translate-y-1/2 opacity-0 group-hover:opacity-100 transition-opacity duration-200 select-none ${isDragging ? 'cursor-grabbing' : 'cursor-grab'}`}
onMouseDown={handleMouseDown}
>
<div className="bg-gray-600 text-white p-1 rounded-l text-xs">
<FaGripVertical />
</div>
</div>
<Button
variant="solid"
size="sm"
2025-10-13 14:48:55 +00:00
className="!rounded-l-full !rounded-r-none"
onClick={(e) => {
e.stopPropagation()
onToggle()
}}
title={isVisible ? 'Paneli kapat' : 'Paneli aç'}
>
<div className="flex items-center gap-2">
{isVisible ? <FaChevronRight /> : <FaChevronLeft />}
{getTotalCount() > 0 && <Badge content={getTotalCount()} />}
</div>
</Button>
</div>
</div>
{/* Overlay */}
{isVisible && (
2025-10-13 21:47:53 +00:00
<div className="fixed inset-0 bg-black bg-opacity-25 z-20" onClick={onToggle} />
)}
{/* Panel */}
<div
className={`fixed right-0 top-0 h-full bg-white border-l border-gray-300 shadow-xl transform transition-transform duration-300 ease-in-out z-30 ${isVisible ? 'translate-x-0' : 'translate-x-full'}`}
style={{ width: '450px' }}
onClick={(e) => e.stopPropagation()}
>
<div className="flex flex-col h-full">
<div className="p-4 border-b border-gray-200 bg-gray-50">
{/* Üst Satır: Başlık, Kayıt Bilgisi Toggle ve Kapat Butonu */}
<div className="flex items-center justify-between mb-3">
<h3 className="text-lg font-semibold text-gray-800">Notlar</h3>
<div className="flex items-center gap-3">
{/* 👇 Kayıt Bilgisi Aç/Kapa Butonu */}
<button
onClick={() => setShowEntityInfo((prev) => !prev)}
className="flex items-center gap-1 text-sm text-gray-600 hover:text-gray-800 cursor-pointer select-none"
title="Kayıt Bilgisi"
>
{showEntityInfo ? <FaChevronUp /> : <FaChevronDown />}
</button>
{/* Kapat Butonu */}
<Button variant="plain" size="xs" onClick={onToggle} title="Paneli kapat">
<FaTimes />
</Button>
</div>
</div>
{/* 👇 Açılır Kayıt Bilgisi İçeriği */}
<div
className={`transition-all duration-300 overflow-hidden ${
showEntityInfo ? 'max-h-20 mt-2 opacity-100' : 'max-h-0 opacity-0'
}`}
>
<div className="flex items-center gap-1 text-sm text-gray-700">
<span className="font-medium">{entityName}</span>
<code className="bg-gray-100 px-2 rounded text-gray-800 text-xs font-mono">
<Badge className="bg-blue-100 text-blue-600" content={entityId} />
</code>
</div>
</div>
{/* Alt buton: Not Ekle */}
<div className="flex gap-2 mt-3">
<Button
variant="solid"
size="xs"
onClick={() => setShowAddModal(true)}
className="flex justify-center items-center py-4 w-full"
>
<FaPlus className="mr-1" /> Not Ekle
</Button>
</div>
</div>
<div className="flex-1 overflow-y-auto p-4">
<NoteList
notes={activities}
onDeleteNote={handleDeleteActivity}
2025-10-13 21:47:53 +00:00
onDownloadFile={handleDownloadFile}
/>
</div>
</div>
</div>
{/* Modal */}
<NoteModal
2025-10-13 21:47:53 +00:00
entityName={entityName}
entityId={entityId}
isOpen={showAddModal}
onClose={() => setShowAddModal(false)}
onNoteAdded={(act) => setActivities((prev) => [act, ...prev])}
/>
</>
)
}