firecms icon indicating copy to clipboard operation
firecms copied to clipboard

Dynamic map

Open dvird opened this issue 2 years ago • 2 comments

Is there way for dynamic map?

i want for example to add to "product" field of "members" the field will be map of userid to role {"adafewr324asfas":"admin}

dvird avatar Apr 04 '22 04:04 dvird

Hi @dvird I am not totally sure what you mean

fgatti675 avatar Apr 07 '22 15:04 fgatti675

today we can only build a map with default keys for example: publisher: { title: "Publisher", description: "This is an example of a map property", dataType: "map", properties: { name: { title: "Name", dataType: "string" }, external_id: { title: "External id", dataType: "string" } } }

i want to put dynamic keys on the cms, i want the key will be user id , and value will be role

dvird avatar Apr 09 '22 02:04 dvird

I have the same problem. I have a restaurant model restaurants:{ restaurantTables : Record<string, RestaurantTable>} and I can't represent it in the CMS. because the CMS doesn't allow to define a property as a key

warrenrhodes avatar Feb 16 '23 14:02 warrenrhodes

Hi guys, this feature is not planned in the near future. We are happy to receive PRs though, and if this is important for your business we are also happy to discuss implementing it as a consulting project, you can reach us at [email protected]

fgatti675 avatar Feb 16 '23 14:02 fgatti675

@fgatti675 I was able to work around this by creating a custom preview and a custom field. Editing, updating and adding work fine, except for deleting. eg: const tables : Record<String, Table> = { 'tableId_1' : {name: 'Africa'}, 'tableId_2' : {name: 'Europe'}, 'tableId_3' : {name: 'Asia'}} ; delete the tables [tableId_3]; delete the tables [tableId_2];. we expect on the database tables: {'tableId_1': {name: 'Africa'}} which is not the case. In fact, the database is not updated but when we print the values contained onPreSave and onSaveSuccess callback function, they contain the correct information but the database is not updated and the preview widget contains this map tables: {'tableId_1': {name: 'Africa'}, 'tableId_2': {name: 'Europe'}}

warrenrhodes avatar Feb 20 '23 10:02 warrenrhodes

Hi guys, it is now possible to add arbitrary key-value data to map properties, feed free to try out!

fgatti675 avatar May 31 '23 15:05 fgatti675

@warrenrhodes found solution for delete, care to share part of you code I am in same problem, thanks

muhamed-didovic avatar Nov 09 '23 13:11 muhamed-didovic

@muhamed-didovic sorry to be late. with part of the code do you want actually?

There the field schema

 restaurantTables: buildProperty<Record<string, unknown>>({
    name: 'Restaurant Tables',
    dataType: 'map',
    validation: { required: false, unique: true },
    Preview: RestaurantTablesPreView,
    Field: RestaurantTablesFields,
  }),

The RestaurantTablesPreView and RestaurantTablesFields code.

export function RestaurantTablesPreView({
  value,
}: PropertyPreviewProps<Partial<Record<string, unknown>>>) {
  const [open, setOpen] = React.useState(false);

  const handleClickSetQrCode = (webDeepLink: string | undefined) => {
    if (!webDeepLink) {
      return;
    }
    window.open(
      `https://chart.googleapis.com/chart?chs=300x300&cht=qr&chl=${webDeepLink}`,
      '_blank'
    );
    return;
  };

  const handleClickOpen = () => {
    setOpen(true);
  };

  const handleClose = () => {
    setOpen(false);
  };

  if (!value) {
    return <Box />;
  }

  const restaurantTables = value as Record<string, RestaurantTable>;

  return (
    <Box
      sx={(theme) => ({
        display: 'flex',
        flexWrap: 'wrap',
        gap: theme.spacing(0.5),
      })}
    >
      <Button variant="outlined" onClick={handleClickOpen}>
        DineIn Table
      </Button>
      <Dialog
        fullWidth={true}
        maxWidth={'lg'}
        open={open}
        onClose={handleClose}
      >
        <DialogContent>
          <TableContainer sx={{ maxHeight: 440 }}>
            <Table stickyHeader aria-label="sticky table">
              <TableHead>
                <TableRow>
                  {columns.map((column) => (
                    <TableCell key={column.id} align={'center'}>
                      {column.label}
                    </TableCell>
                  ))}
                </TableRow>
              </TableHead>
              <TableBody>
                {Object.entries(restaurantTables).map(([key, table]) => {
                  return (
                    <TableRow key={key}>
                      <TableCell>{table.dineInTable}</TableCell>
                      <TableCell align="center">
                        {table.dineInWebDeepLink}
                      </TableCell>
                      <TableCell align="center">
                        {table.dineInWebDeepLink ? (
                          <Button
                            variant="contained"
                            sx={{ marginY: '10px' }}
                            onClick={() =>
                              handleClickSetQrCode(table.dineInWebDeepLink)
                            }
                          >
                            Display QrCode
                          </Button>
                        ) : (
                          <Box />
                        )}
                      </TableCell>
                    </TableRow>
                  );
                })}
              </TableBody>
            </Table>
          </TableContainer>
        </DialogContent>
      </Dialog>
    </Box>
  );
}

export function RestaurantTablesFields({
  value,
  isSubmitting,
  setValue,
  showError,
  error,
}: FieldProps<Partial<Record<string, unknown>>>) {
  const [restaurantTable, setRestaurantTable] = useState<
    Record<string, RestaurantTable> | undefined
  >(value as Record<string, RestaurantTable> | undefined);

  /**
   * Adds a new dineIn table.
   */
  const addNewDineInTable = () => {
    setRestaurantTable({
      ...restaurantTable,
      [generateUUID()]: {
        dineInTable: '',
      },
    });
  };

  /**
   * Updates a dineIn table.
   */
  const updateDineInTable = (value: string, tableKey: string) => {
    if (!restaurantTable) return;
    restaurantTable[tableKey].dineInTable = value;
  };

  /**
   * Updates the restaurant table on submitting.
   * @returns { void }.
   */
  const updateRestaurantTable = useCallback(async () => {
    if (restaurantTable) {
      const restaurantTables = Object.values(restaurantTable);
      for (let i = 0; i < restaurantTables.length; i++) {
        const name = restaurantTables[i].dineInTable;
        if (!name || name.trim().length === 0) {
          return;
        }

        for (let j = i + 1; j < restaurantTables.length; j++) {
          if (
            restaurantTables[i].dineInTable === restaurantTables[j].dineInTable
          ) {
            return;
          }
        }
      }

      setValue(
        restaurantTable && Object.values(restaurantTable).length !== 0
          ? restaurantTable
          : null
      );
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [restaurantTable]);

  useEffect(() => {
    if (isSubmitting) {
      updateRestaurantTable();
    }
  }, [isSubmitting, updateRestaurantTable]);

  return (
    <Paper variant="outlined" sx={{ width: '100%', padding: '12px' }}>
      <List sx={{ width: '100%', bgcolor: 'background.paper' }}>
        {restaurantTable ? (
          Object.entries(restaurantTable).map(([key, value]) => {
            return (
              <Card
                sx={{
                  margin: '10px',
                  padding: '10px',
                }}
              >
                <Stack direction={'row'} alignItems={'center'}>
                  <FormControl error={!!error}>
                    <TextField
                      key={value.dineInTable}
                      id="outlined-basic"
                      label="Table Name"
                      variant="outlined"
                      defaultValue={value.dineInTable}
                      required
                      onChange={(value) =>
                        updateDineInTable(value.target.value, key)
                      }
                    />
                    {showError && <FormHelperText>{error}</FormHelperText>}
                  </FormControl>
                </Stack>
              </Card>
            );
          })
        ) : (
          <Box />
        )}
      </List>
      <Button
        variant="outlined"
        color="primary"
        size="medium"
        startIcon={<AddIcon />}
        onClick={addNewDineInTable}
      >
        Add
      </Button>
    </Paper>
  );
}

I hope this will help you.

warrenrhodes avatar Nov 13 '23 15:11 warrenrhodes