buster/web/src/controllers/TermIndividualController/TermIndividualContent.tsx

212 lines
6.4 KiB
TypeScript

'use client';
import React, { useEffect, useMemo, useState } from 'react';
import { AppContent } from '@/components/ui/layout/AppContent';
import { useBusterTermsIndividualContextSelector, useBusterTermsIndividual } from '@/context/Terms';
import { Dropdown, Input } from 'antd';
import { useDebounceFn } from 'ahooks';
import { formatDate } from '@/lib';
import { AppMaterialIcons, EditableTitle } from '@/components/ui';
import { useAntToken } from '@/styles/useAntToken';
import { AppCodeEditor } from '@/components/ui/inputs/AppCodeEditor';
import clamp from 'lodash/clamp';
import { MenuProps } from 'antd/lib';
import { ItemContainer } from '@/components/ui/card/ItemContainer';
import { Text } from '@/components/ui';
import { BusterRoutes } from '@/routes';
import { useAppLayoutContextSelector } from '@/context/BusterAppLayout';
export const TermIndividualContent: React.FC<{
termId: string;
}> = ({ termId }) => {
const updateTerm = useBusterTermsIndividualContextSelector((x) => x.updateTerm);
const { term: selectedTerm } = useBusterTermsIndividual({ termId });
const loadingSelectedTerm = !selectedTerm?.id;
const [editingTermName, setEditingTermName] = useState(false);
const [termName, setTermName] = useState(selectedTerm?.name || '');
const [termDefinition, setTermDefinition] = useState(selectedTerm?.definition || '');
const [termSQL, setTermSQL] = useState(selectedTerm?.sql_snippet || '');
const [sqlHeight, setSqlHeight] = useState(300);
const onSetTermName = (value: string) => {
setTermName(value);
updateTerm({
id: termId,
name: value
});
};
const onSetTermDefinition = (value: string) => {
setTermDefinition(value);
updateTerm({
id: termId,
definition: value
});
};
const onSetTermSQL = useDebounceFn(
(value: string) => {
setTermSQL(value);
updateTerm({
id: termId,
sql_snippet: value
});
},
{ wait: 500 }
);
useEffect(() => {
setTermName(selectedTerm?.name || '');
setTermDefinition(selectedTerm?.definition || '');
setTermSQL(selectedTerm?.sql_snippet || '');
}, [selectedTerm?.name, selectedTerm?.definition]);
return (
<AppContent className="overflow-auto p-8">
{loadingSelectedTerm ? (
<SkeletonLoader />
) : (
<div className="flex flex-col">
<div className="flex justify-between space-x-3">
<div className="mb-5 flex flex-col space-y-0.5">
<div className={'overflow-hidden'}>
<EditableTitle
editing={editingTermName}
onEdit={setEditingTermName}
onChange={(v) => {
onSetTermName(v);
}}
level={4}>
{termName}
</EditableTitle>
</div>
<div>
<Text type="secondary">
Last updated:{' '}
{formatDate({
date: selectedTerm?.updated_at!,
format: 'lll'
})}
</Text>
</div>
</div>
<div>
<MoreDropdown termId={termId} setEditingTermName={setEditingTermName} />
</div>
</div>
<div className="flex flex-col space-y-4">
<ItemContainer title="Definition">
<div className={'overflow-hidden'}>
<Input.TextArea
key={selectedTerm?.id || 'default'}
defaultValue={selectedTerm?.definition || termDefinition}
autoSize={{ minRows: 3, maxRows: 20 }}
placeholder={'Enter definition...'}
onBlur={(e) => {
onSetTermDefinition(e.target.value);
}}
variant="borderless"
/>
</div>
</ItemContainer>
<ItemContainer
title={
<div className="flex w-full items-center justify-between space-x-2">
<Text>SQL Snippet</Text>
<AppMaterialIcons className="cursor-pointer" size={18} icon="help" />
</div>
}>
<div className="relative h-full w-full" style={{ height: sqlHeight }}>
<AppCodeEditor
style={{ minHeight: sqlHeight }}
defaultValue={termSQL}
onChangeEditorHeight={(v) => {
setSqlHeight(clamp(v, 300, 800));
}}
onChange={(v) => {
onSetTermSQL.run(v);
}}
monacoEditorOptions={{
scrollbar: {
alwaysConsumeMouseWheel: false
}
}}
/>
</div>
</ItemContainer>
</div>
</div>
)}
</AppContent>
);
};
const SkeletonLoader: React.FC = () => {
return <div className="p-4">{/* <Skeleton /> */}</div>;
};
const MoreDropdown: React.FC<{ termId: string; setEditingTermName: (value: boolean) => void }> = ({
termId,
setEditingTermName
}) => {
const token = useAntToken();
const onDeleteTerm = useBusterTermsIndividualContextSelector((x) => x.onDeleteTerm);
const onChangePage = useAppLayoutContextSelector((s) => s.onChangePage);
const onDeleteTermsPreflight = async () => {
await onDeleteTerm({ ids: [termId] })
.then(() => {
onChangePage({
route: BusterRoutes.APP_TERMS
});
})
.catch((error) => {
//
});
};
const dropdownItems: MenuProps['items'] = useMemo(
() => [
{
key: 'edit',
icon: <AppMaterialIcons size={14} icon="edit" />,
label: 'Edit term title',
onClick: () => {
setEditingTermName(true);
}
},
{
key: 'delete',
icon: <AppMaterialIcons size={14} icon="delete" />,
label: 'Delete term',
onClick: onDeleteTermsPreflight
}
],
[setEditingTermName, onDeleteTermsPreflight]
);
const menu = useMemo(() => {
return {
items: dropdownItems
};
}, [dropdownItems]);
return (
<Dropdown trigger={['click']} menu={menu}>
<div
className="h-fit! cursor-pointer"
style={{
height: 18,
color: token.colorIcon
}}>
<AppMaterialIcons size={18} icon="more_horiz" />
</div>
</Dropdown>
);
};