• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

teableio / teable / 8538004962

03 Apr 2024 11:36AM UTC coverage: 18.233% (-3.3%) from 21.535%
8538004962

Pull #528

github

web-flow
Merge c1a248a6f into 45ee7ebb3
Pull Request #528: feat: Kanban view

575 of 1136 branches covered (50.62%)

29 of 2908 new or added lines in 83 files covered. (1.0%)

5 existing lines in 5 files now uncovered.

6439 of 35315 relevant lines covered (18.23%)

3.94 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/packages/sdk/src/components/select-field-dialog/FieldCreateOrSelectModal.tsx
NEW
1
import { createFieldRoSchema, getUniqName } from '@teable/core';
×
NEW
2
import type { IFieldVo, FieldType, IFieldRo } from '@teable/core';
×
NEW
3
import { ArrowLeft, Plus } from '@teable/icons';
×
NEW
4
import {
×
NEW
5
  Button,
×
NEW
6
  Dialog,
×
NEW
7
  DialogClose,
×
NEW
8
  DialogContent,
×
NEW
9
  DialogDescription,
×
NEW
10
  DialogFooter,
×
NEW
11
  DialogHeader,
×
NEW
12
  DialogTitle,
×
NEW
13
  DialogTrigger,
×
NEW
14
  Label,
×
NEW
15
  RadioGroup,
×
NEW
16
  RadioGroupItem,
×
NEW
17
  ScrollArea,
×
NEW
18
  cn,
×
NEW
19
} from '@teable/ui-lib';
×
NEW
20
import { forwardRef, useEffect, useImperativeHandle, useMemo, useState } from 'react';
×
NEW
21
import { useTranslation } from '../../context/app/i18n';
×
NEW
22
import { useFieldStaticGetter, useFields, useTableId } from '../../hooks';
×
NEW
23
import type { IFieldInstance } from '../../model';
×
NEW
24
import { Field } from '../../model';
×
NEW
25
import { FieldCreator } from './FieldCreator';
×
NEW
26

×
NEW
27
interface IFieldCreateOrSelectModalProps {
×
NEW
28
  title: React.ReactNode;
×
NEW
29
  content?: React.ReactNode;
×
NEW
30
  description?: React.ReactNode;
×
NEW
31
  fieldTypes: FieldType[];
×
NEW
32
  selectedFieldId?: string;
×
NEW
33
  isMultipleEnable?: boolean;
×
NEW
34
  children: (isActive: boolean) => React.ReactNode;
×
NEW
35
  onConfirm?: (field: IFieldVo | IFieldInstance) => void;
×
NEW
36
}
×
NEW
37

×
NEW
38
export interface IFieldCreateOrSelectModalRef {
×
NEW
39
  onOpen: () => void;
×
NEW
40
  onClose: () => void;
×
NEW
41
}
×
NEW
42

×
NEW
43
export const FieldCreateOrSelectModal = forwardRef<
×
NEW
44
  IFieldCreateOrSelectModalRef,
×
NEW
45
  IFieldCreateOrSelectModalProps
×
NEW
46
>((props, forwardRef) => {
×
NEW
47
  const {
×
NEW
48
    title,
×
NEW
49
    content,
×
NEW
50
    description,
×
NEW
51
    fieldTypes,
×
NEW
52
    selectedFieldId: _selectedFieldId,
×
NEW
53
    isMultipleEnable,
×
NEW
54
    children,
×
NEW
55
    onConfirm,
×
NEW
56
  } = props;
×
NEW
57
  const tableId = useTableId();
×
NEW
58
  const totalFields = useFields({ withHidden: true });
×
NEW
59
  const getFieldStatic = useFieldStaticGetter();
×
NEW
60
  const [newField, setNewField] = useState<IFieldRo>();
×
NEW
61
  const { t } = useTranslation();
×
NEW
62
  const [selectedFieldId, setSelectedFieldId] = useState<string | undefined>(_selectedFieldId);
×
NEW
63
  const [open, setOpen] = useState(false);
×
NEW
64

×
NEW
65
  useImperativeHandle(forwardRef, () => ({
×
NEW
66
    onOpen: () => setOpen(true),
×
NEW
67
    onClose: () => setOpen(false),
×
NEW
68
  }));
×
NEW
69

×
NEW
70
  useEffect(() => {
×
NEW
71
    setSelectedFieldId(_selectedFieldId);
×
NEW
72
  }, [_selectedFieldId]);
×
NEW
73

×
NEW
74
  const filteredFields = useMemo(() => {
×
NEW
75
    return totalFields.filter(({ type, isMultipleCellValue }) => {
×
NEW
76
      if (isMultipleEnable) {
×
NEW
77
        return fieldTypes.includes(type);
×
NEW
78
      }
×
NEW
79
      return !isMultipleCellValue && fieldTypes.includes(type);
×
NEW
80
    });
×
NEW
81
  }, [fieldTypes, totalFields, isMultipleEnable]);
×
NEW
82

×
NEW
83
  const onNewFieldEdit = (field: IFieldRo) => {
×
NEW
84
    const { name: originName } = field;
×
NEW
85
    const allExistNames = totalFields.map(({ name }) => name);
×
NEW
86
    const name = getUniqName(originName as string, allExistNames);
×
NEW
87

×
NEW
88
    setNewField({ ...field, name });
×
NEW
89
    setSelectedFieldId(undefined);
×
NEW
90
  };
×
NEW
91

×
NEW
92
  const onFieldSelect = (value: string) => {
×
NEW
93
    setSelectedFieldId(value);
×
NEW
94
  };
×
NEW
95

×
NEW
96
  const onConfirmInner = async () => {
×
NEW
97
    if (newField != null) {
×
NEW
98
      if (tableId == null) return setNewField(undefined);
×
NEW
99
      const result = createFieldRoSchema.safeParse(newField);
×
NEW
100
      if (result.success) {
×
NEW
101
        const field = (await Field.createField(tableId, newField)).data;
×
NEW
102
        setNewField(undefined);
×
NEW
103
        return onConfirm?.(field);
×
NEW
104
      }
×
NEW
105
      return setNewField(undefined);
×
NEW
106
    }
×
NEW
107
    if (selectedFieldId != null) {
×
NEW
108
      const selectedField = totalFields.find((field) => field.id === selectedFieldId);
×
NEW
109
      return selectedField ? onConfirm?.(selectedField) : undefined;
×
NEW
110
    }
×
NEW
111
  };
×
NEW
112

×
NEW
113
  return (
×
NEW
114
    <Dialog open={open} onOpenChange={setOpen}>
×
NEW
115
      <DialogTrigger asChild>{children(open)}</DialogTrigger>
×
NEW
116
      <DialogContent
×
NEW
117
        className="p-5"
×
NEW
118
        closeable={false}
×
NEW
119
        onInteractOutside={(e) => e.preventDefault()}
×
NEW
120
      >
×
NEW
121
        <DialogHeader className="space-y-2">
×
NEW
122
          <DialogTitle>{title}</DialogTitle>
×
NEW
123
          {description && <DialogDescription className="text-xs">{description}</DialogDescription>}
×
NEW
124
        </DialogHeader>
×
NEW
125

×
NEW
126
        <ScrollArea className="h-36 w-full" type="always">
×
NEW
127
          <div className="px-2">
×
NEW
128
            {newField ? (
×
NEW
129
              <FieldCreator field={newField} setField={setNewField} />
×
NEW
130
            ) : (
×
NEW
131
              <RadioGroup className="gap-4" value={selectedFieldId} onValueChange={onFieldSelect}>
×
NEW
132
                {filteredFields.map((field) => {
×
NEW
133
                  const { id, type, name, isLookup } = field;
×
NEW
134
                  const { Icon } = getFieldStatic(type, isLookup);
×
NEW
135
                  return (
×
NEW
136
                    <div key={id} className="flex items-center space-x-2">
×
NEW
137
                      <RadioGroupItem value={id} id={id} />
×
NEW
138
                      <Label className="flex cursor-pointer items-center space-x-1" htmlFor={id}>
×
NEW
139
                        <Icon className="size-4" />
×
NEW
140
                        <span>{name}</span>
×
NEW
141
                      </Label>
×
NEW
142
                    </div>
×
NEW
143
                  );
×
NEW
144
                })}
×
NEW
145
              </RadioGroup>
×
NEW
146
            )}
×
NEW
147
          </div>
×
NEW
148
        </ScrollArea>
×
NEW
149

×
NEW
150
        {!newField && (
×
NEW
151
          <div className="flex flex-col space-y-2">
×
NEW
152
            {fieldTypes.map((type) => {
×
NEW
153
              const { title, Icon, defaultOptions } = getFieldStatic(type, false);
×
NEW
154
              return (
×
NEW
155
                <Button
×
NEW
156
                  key={type}
×
NEW
157
                  variant="secondary"
×
NEW
158
                  className="justify-start"
×
NEW
159
                  onClick={() =>
×
NEW
160
                    onNewFieldEdit({
×
NEW
161
                      type,
×
NEW
162
                      name: title,
×
NEW
163
                      options: defaultOptions,
×
NEW
164
                    } as IFieldRo)
×
NEW
165
                  }
×
NEW
166
                >
×
NEW
167
                  <Plus className="size-5" />
×
NEW
168
                  <Icon className="size-4" />
×
NEW
169
                  {`Create a new ${title} field`}
×
NEW
170
                </Button>
×
NEW
171
              );
×
NEW
172
            })}
×
NEW
173
          </div>
×
NEW
174
        )}
×
NEW
175

×
NEW
176
        {!newField && content}
×
NEW
177

×
NEW
178
        <DialogFooter className={cn(newField && 'justify-between sm:justify-between')}>
×
NEW
179
          {newField && (
×
NEW
180
            <Button variant={'ghost'} onClick={() => setNewField(undefined)}>
×
NEW
181
              <ArrowLeft />
×
NEW
182
              {t('common.back')}
×
NEW
183
            </Button>
×
NEW
184
          )}
×
NEW
185
          <DialogClose asChild>
×
NEW
186
            <Button disabled={!selectedFieldId && !newField} onClick={onConfirmInner}>
×
NEW
187
              {t('common.done')}
×
NEW
188
            </Button>
×
NEW
189
          </DialogClose>
×
NEW
190
        </DialogFooter>
×
NEW
191
      </DialogContent>
×
NEW
192
    </Dialog>
×
NEW
193
  );
×
NEW
194
});
×
NEW
195

×
NEW
196
FieldCreateOrSelectModal.displayName = 'FieldCreateOrSelectModal';
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc