erp-platform/ui/src/views/list/ChartDrawer.tsx

369 lines
16 KiB
TypeScript
Raw Normal View History

2025-11-30 22:45:43 +00:00
import { Button, FormContainer, Input, Notification, Select, toast } from '@/components/ui'
import { Field, FieldArray, Form, Formik, FieldProps } from 'formik'
import { FaMinus, FaPlus, FaTimes, FaSave } from 'react-icons/fa'
import { SelectBoxOption } from '@/types/shared'
import { columnSummaryTypeListOptions } from '../admin/listForm/edit/options'
import { ChartSeriesDto } from '@/proxy/admin/charts/models'
import { useLocalization } from '@/utils/hooks/useLocalization'
import { useStoreState } from '@/store/store'
import { object, array, string } from 'yup'
import { SummaryTypeEnum } from '@/proxy/form/models'
import { useState, useEffect } from 'react'
interface ChartDrawerProps {
open: boolean
onClose: () => void
initialSeries: ChartSeriesDto[]
fieldList: SelectBoxOption[]
onSave: (series: ChartSeriesDto[]) => void
onPreviewChange: (series: ChartSeriesDto[]) => void
}
const schema = object().shape({
series: array().of(
object().shape({
name: string().required('Name Required'),
argumentField: string().required('Argument Field Required'),
valueField: string().required('Value Field Required'),
summaryType: string().required('Summary Type Required'),
}),
),
})
const ChartDrawer = ({
open,
onClose,
initialSeries,
fieldList,
onSave,
onPreviewChange,
}: ChartDrawerProps) => {
const { translate } = useLocalization()
const { userName } = useStoreState((s) => s.auth.user)
const [selectedSeriesIndex, setSelectedSeriesIndex] = useState<number>(-1)
// Chart type icons
const chartTypeIcons = [
{ type: 'line', label: 'Line', icon: '📈' },
{ type: 'bar', label: 'Bar', icon: '📊' },
{ type: 'area', label: 'Area', icon: '🏔️' },
{ type: 'scatter', label: 'Scatter', icon: '🔵' },
{ type: 'spline', label: 'Spline', icon: '〰️' },
{ type: 'stackedbar', label: 'Stacked Bar', icon: '📚' },
{ type: 'stackedarea', label: 'Stacked Area', icon: '⛰️' },
]
const newSeriesValue = () => {
return {
index: -1,
type: 'line',
name: '',
argumentField: '',
valueField: '',
summaryType: SummaryTypeEnum.Sum,
axis: '',
barOverlapGroup: '',
barPadding: 0,
barWidth: 0,
color: '',
cornerRadius: 0,
dashStyle: 'solid',
ignoreEmptyPoints: false,
pane: '',
rangeValue1Field: '',
rangeValue2Field: '',
selectionMode: 'none',
showInLegend: true,
visible: true,
width: 2,
label: {
visible: true,
backgroundColor: '#f05b41',
customizeText: '',
format: 'decimal',
font: {
color: '#FFFFFF',
family: '"Segoe UI", "Helvetica Neue", "Trebuchet MS", Verdana, sans-serif',
size: 12,
weight: 400,
},
},
userId: userName ?? '',
}
}
if (!open) return null
return (
<>
{/* Drawer */}
<div
className={`fixed right-0 top-0 h-full w-[500px] bg-white shadow-2xl z-50 transform transition-transform duration-300 border-l-2 border-gray-300 ${
open ? 'translate-x-0' : 'translate-x-full'
}`}
>
<Formik
enableReinitialize
initialValues={{
series: initialSeries && initialSeries.length > 0 ? initialSeries : [newSeriesValue()],
}}
validationSchema={schema}
onSubmit={async (values, { setSubmitting }) => {
try {
await onSave(values.series)
toast.push(<Notification type="success">Chart kaydedildi</Notification>, {
placement: 'top-end',
})
onClose()
} catch (error: any) {
toast.push(
<Notification type="danger">
Hata
<code>{error}</code>
</Notification>,
{ placement: 'top-end' },
)
} finally {
setSubmitting(false)
}
}}
>
{({ setFieldValue, values, isSubmitting }) => {
// Preview'ı her değişiklikte otomatik güncelle
useEffect(() => {
const validSeries = values.series.filter(
(s) => s.argumentField && s.valueField && s.summaryType,
)
if (validSeries.length > 0) {
onPreviewChange(validSeries)
}
}, [values.series])
return (
<Form className="flex flex-col h-full">
{/* Header */}
<div className="flex items-center justify-between p-4 border-b bg-gray-50">
<h2 className="text-lg font-semibold flex items-center gap-2">
<span>📊</span>
Chart Series
</h2>
<button
type="button"
onClick={onClose}
className="p-2 hover:bg-gray-200 rounded-full transition-colors"
>
<FaTimes />
</button>
</div>
<FormContainer size="sm" className="flex flex-col flex-1 overflow-hidden">
{/* Add Series Button */}
<div className="p-3 border-b">
<Button
variant="solid"
type="button"
size="sm"
onClick={() => {
setFieldValue('series', [...values.series, newSeriesValue()])
}}
className="w-full"
>
<div className="flex items-center justify-center gap-2">
<FaPlus /> Yeni Seri Ekle
</div>
</Button>
</div>
{/* Scrollable Content */}
<div className="flex-1 overflow-y-auto p-3">
<FieldArray name="series">
{({ remove }) => (
<div className="space-y-4">
{values.series.map((series, index) => (
<div
key={index}
className="border rounded-lg p-3 bg-gray-50 hover:bg-gray-100 transition-colors"
>
{/* Header */}
<div className="flex items-center justify-between mb-3">
<span className="font-semibold text-sm">Seri #{index + 1}</span>
<Button
type="button"
size="xs"
variant="plain"
icon={<FaMinus />}
className="text-red-500 hover:bg-red-100"
onClick={() => {
remove(index)
}}
/>
</div>
{/* Chart Type Selector */}
<div className="mb-3">
<label className="text-xs font-medium mb-1 block">Chart Type</label>
<Field name={`series[${index}].type`}>
{({ field, form }: FieldProps) => (
<div className="relative">
<button
type="button"
onClick={() =>
setSelectedSeriesIndex(
selectedSeriesIndex === index ? -1 : index,
)
}
className="w-full px-3 py-2 text-left border rounded hover:bg-white flex items-center gap-2 transition-colors"
>
<span className="text-xl">
{chartTypeIcons.find((t) => t.type === field.value)
?.icon || '📊'}
</span>
<span className="text-sm font-medium">
{chartTypeIcons.find((t) => t.type === field.value)
?.label || field.value}
</span>
</button>
{selectedSeriesIndex === index && (
<div className="absolute z-50 mt-1 w-full bg-white border rounded-lg shadow-xl p-2">
<div className="grid grid-cols-2 gap-2">
{chartTypeIcons.map((chartType) => (
<button
key={chartType.type}
type="button"
onClick={() => {
form.setFieldValue(field.name, chartType.type)
setSelectedSeriesIndex(-1)
}}
className={`p-3 rounded-lg hover:bg-blue-50 flex flex-col items-center gap-1 transition-all ${
field.value === chartType.type
? 'bg-blue-100 ring-2 ring-blue-400'
: 'bg-gray-50'
}`}
>
<span className="text-2xl">{chartType.icon}</span>
<span className="text-xs font-medium text-center">
{chartType.label}
</span>
</button>
))}
</div>
</div>
)}
</div>
)}
</Field>
</div>
{/* Name */}
<div className="mb-3">
<label className="text-xs font-medium mb-1 block">Name</label>
<Field
size="sm"
name={`series[${index}].name`}
type="text"
component={Input}
placeholder="Series name"
/>
</div>
{/* Argument Field */}
<div className="mb-3">
<label className="text-xs font-medium mb-1 block">
Argument Field (X-Axis)
</label>
<Field name={`series[${index}].argumentField`}>
{({ field, form }: FieldProps) => (
<Select
field={field}
form={form}
isClearable={true}
options={fieldList}
value={fieldList?.find(
(option) => option.value === field.value,
)}
onChange={(option) => {
form.setFieldValue(field.name, option?.value)
}}
placeholder="Select field..."
/>
)}
</Field>
</div>
{/* Value Field */}
<div className="mb-3">
<label className="text-xs font-medium mb-1 block">
Value Field (Y-Axis)
</label>
<Field name={`series[${index}].valueField`}>
{({ field, form }: FieldProps) => (
<Select
field={field}
form={form}
isClearable={true}
options={fieldList}
value={fieldList?.find(
(option) => option.value === field.value,
)}
onChange={(option) => {
form.setFieldValue(field.name, option?.value)
}}
placeholder="Select field..."
/>
)}
</Field>
</div>
{/* Summary Type */}
<div className="mb-3">
<label className="text-xs font-medium mb-1 block">
Summary Type
</label>
<Field name={`series[${index}].summaryType`}>
{({ field, form }: FieldProps) => (
<Select
field={field}
form={form}
isClearable={true}
options={columnSummaryTypeListOptions}
value={columnSummaryTypeListOptions.find(
(option) => option.value === field.value,
)}
onChange={(option) => {
form.setFieldValue(field.name, option?.value)
}}
/>
)}
</Field>
</div>
</div>
))}
</div>
)}
</FieldArray>
</div>
{/* Footer */}
<div className="p-4 border-t bg-gray-50 flex gap-2">
<Button type="button" variant="plain" onClick={onClose} className="flex-1">
İptal
</Button>
<Button variant="solid" loading={isSubmitting} type="submit" className="flex-1">
<div className="flex items-center justify-center gap-2">
<FaSave />
{isSubmitting ? translate('::SavingWithThreeDot') : 'Kaydet'}
</div>
</Button>
</div>
</FormContainer>
</Form>
)
}}
</Formik>
</div>
</>
)
}
export default ChartDrawer