erp-platform/ui/src/views/form/notes/NotePanel.tsx
2025-11-14 23:59:46 +03:00

230 lines
7.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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)
}
}
useEffect(() => {
if (isVisible) fetchActivities()
}, [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
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"
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 && (
<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}
onDownloadFile={handleDownloadFile}
/>
</div>
</div>
</div>
{/* Modal */}
<NoteModal
entityName={entityName}
entityId={entityId}
isOpen={showAddModal}
onClose={() => setShowAddModal(false)}
onNoteAdded={(act) => setActivities((prev) => [act, ...prev])}
/>
</>
)
}