sozsoft-platform/ui/src/views/list/ChartDrawer.tsx
2026-05-05 20:59:30 +03:00

376 lines
17 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 { chartSeriesTypeOptions, 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 ChartDrawer = ({
open,
onClose,
initialSeries,
fieldList,
onSave,
onPreviewChange,
}: ChartDrawerProps) => {
const { translate } = useLocalization()
const { userName } = useStoreState((s) => s.auth.user)
const [selectedSeriesIndex, setSelectedSeriesIndex] = useState<number>(-1)
const schema = object().shape({
series: array().of(
object().shape({
name: string().required(translate('::App.Platform.ChartDrawer.NameRequired')),
argumentField: string().required(
translate('::App.Platform.ChartDrawer.ArgumentFieldRequired'),
),
valueField: string().required(translate('::App.Platform.ChartDrawer.ValueFieldRequired')),
summaryType: string().required(translate('::App.Platform.ChartDrawer.SummaryTypeRequired')),
}),
),
})
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">
{translate('::App.Platform.ChartDrawer.ChartSaved')}
</Notification>,
{
placement: 'top-end',
},
)
onClose()
} catch (error: any) {
toast.push(
<Notification type="danger">
{translate('::App.Platform.Error')}
<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>
{translate('::App.Platform.ChartDrawer.ChartSeries')}
</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 /> {translate('::App.Platform.ChartDrawer.AddNewSeries')}
</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">
{translate('::App.Platform.ChartDrawer.Series')} #{index + 1}
</span>
<Button
type="button"
size="sm"
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">
{translate('::App.Platform.ChartDrawer.ChartType')}
</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">
{chartSeriesTypeOptions.find((t) => t.label === field.value)
?.icon || '📊'}
</span>
<span className="text-sm font-medium">
{chartSeriesTypeOptions.find((t) => t.label === 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">
{chartSeriesTypeOptions.map((chartType) => (
<Button
key={chartType.label}
type="button"
onClick={() => {
form.setFieldValue(field.name, chartType.label)
setSelectedSeriesIndex(-1)
}}
className={`p-2 rounded-lg hover:bg-blue-50 flex items-center gap-2 transition-all ${
field.value === chartType.label
? 'bg-blue-100 ring-2 ring-blue-400'
: 'bg-gray-50'
}`}
>
<span className="text-xl">{chartType.icon}</span>
<span className="text-xs font-medium">
{chartType.label}
</span>
</Button>
))}
</div>
</div>
)}
</div>
)}
</Field>
</div>
{/* Name */}
<div className="mb-3">
<label className="text-xs font-medium mb-1 block">
{translate('::App.Listform.ListformField.Name')}
</label>
<Field
size="sm"
name={`series[${index}].name`}
type="text"
component={Input}
placeholder={translate('::App.Platform.ChartDrawer.SeriesName')}
/>
</div>
{/* Argument Field */}
<div className="mb-3">
<label className="text-xs font-medium mb-1 block">
{translate('::App.Platform.ChartDrawer.ArgumentField')}
</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={translate(
'::App.Platform.ChartDrawer.SelectField',
)}
/>
)}
</Field>
</div>
{/* Value Field */}
<div className="mb-3">
<label className="text-xs font-medium mb-1 block">
{translate('::App.Platform.ChartDrawer.ValueField')}
</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={translate(
'::App.Platform.ChartDrawer.SelectField',
)}
/>
)}
</Field>
</div>
{/* Summary Type */}
<div className="mb-3">
<label className="text-xs font-medium mb-1 block">
{translate('::App.Platform.ChartDrawer.SummaryType')}
</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">
{translate('::Cancel')}
</Button>
<Button variant="solid" loading={isSubmitting} type="submit" className="flex-1">
<div className="flex items-center justify-center gap-2">
<FaSave />
{isSubmitting
? translate('::SavingWithThreeDot')
: translate('::App.Platform.ChartDrawer.Save')}
</div>
</Button>
</div>
</FormContainer>
</Form>
)
}}
</Formik>
</div>
</>
)
}
export default ChartDrawer