erp-platform/ui/src/views/list/ChartDrawer.tsx
2025-12-01 01:45:43 +03:00

368 lines
16 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 { 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