@mui/material#Chip TypeScript Examples

The following examples show how to use @mui/material#Chip. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: CustomChip.tsx    From firecms with MIT License 6 votes vote down vote up
/**
 * @category Preview components
 */
export function CustomChip({
                               colorSeed,
                               label,
                               colorSchemaKey,
                               error,
                               outlined,
                               small
                           }: EnumChipProps) {

    const schema = useMemo(() =>
        colorSchemaKey
            ? getColorSchemeForKey(colorSchemaKey)
            : getColorSchemeForSeed(colorSeed), [colorSeed, colorSchemaKey]);
    const classes = useStyles({ schema, error });

    return (
        <Chip
            classes={{
                root: classes.root,
                label: classes.label
            }}
            size={small ? "small" : "medium"}
            variant={outlined ? "outlined" : "filled"}
            label={label}
        />
    );
}
Example #2
Source File: Breadcrumbs.tsx    From Cromwell with MIT License 6 votes vote down vote up
StyledBreadcrumb = withStyles(() => ({
  root: {
    cursor: 'pointer',
    backgroundColor: 'rgba(0, 0, 0, 0.08)',
    height: '24px',
    color: '#424242',
    fontWeight: 400,
    '&:hover, &:focus': {
      backgroundColor: '#757575',
      color: '#fff',
    },
    '&:active': {
      boxShadow: ' 0px 2px 1px -1px rgb(0 0 0 / 20%), 0px 1px 1px 0px rgb(0 0 0 / 14%), 0px 1px 3px 0px rgb(0 0 0 / 12%);',
      backgroundColor: '#757575',
      color: '#fff',
    },
  },
}))(Chip) as typeof Chip
Example #3
Source File: accountCard.tsx    From Search-Next with GNU General Public License v3.0 6 votes vote down vote up
AccountCard: React.FC<AccoundCardProps> = ({ account }) => {
  return (
    <div className="flex py-4 pl-0 items-center">
      <div className="mr-4">
        <Avatar sx={{ width: 64, height: 64 }}>
          {account.username?.split('')[0].toLocaleUpperCase()}
        </Avatar>
      </div>
      <div>
        <div className="text-lg font-bold leading-7 mb-0">
          {account.username}
          <Chip
            className="ml-2"
            size="small"
            color="info"
            label={account.type === 'local' ? '本地账户' : '注册账户'}
          ></Chip>
        </div>
        <div className="text-sm mt-1 mb-0">
          创建时间: {dayjs(account.createdTime).format('YYYY-MM-DD HH:mm')}
        </div>
      </div>
    </div>
  );
}
Example #4
Source File: connected-status.tsx    From sdk with MIT License 6 votes vote down vote up
export function ConnectedStatus({ state }: IConnectedStatusProps) {
	const {environment} = useContext(EnvironmentContext)
	return (
		<Stack direction="row" alignItems="center" spacing={2}>
			<Tooltip title="SDK Connection Environment" placement="bottom">
				<Chip
					size="small"
					color="info"
					label={getEnvironmentName(environment)}
					sx={{
						lineHeight: 1.1,
						height: "20px",
						fontSize: "0.8125rem"
					}}
				/>
			</Tooltip>
			<Box sx={{ display: "inline" }}>
				<Typography variant="subtitle1" >Connected </Typography>
				<Typography variant="subtitle2">
					<Address address={state.connection.address}/>
				</Typography>
			</Box>
			<IconButton
				color="inherit"
				title="Disconnect"
				onClick={state.disconnect}
			>
				<Icon icon={faLinkSlash}/>
			</IconButton>
		</Stack>
	)
}
Example #5
Source File: transaction-info.tsx    From sdk with MIT License 6 votes vote down vote up
export function TransactionPending({ transaction }: ITransactionInfoProps) {
	const [state, setState] = useState<"resolve" | "reject" | "pending">("pending")
	useEffect( () => {
		transaction.wait()
			.then(() => setState("resolve"))
			.catch(() => setState("reject"))
	}, [transaction])

	return <Box sx={{ my: 1 }}>
		<>
			{ state === "pending" && <><CircularProgress size={14}/> Processing</> }
			{ state === "resolve" && <Chip
				label="Confirmed"
				icon={<Icon icon={faCheckDouble}/>}
				variant="outlined"
				color="success"
				size="small"
			/> }
			{ state === "reject" && <Chip
				label="Rejected"
                icon={<Icon icon={faTimes}/>}
                variant="outlined"
                color="error"
				size="small"
			/> }
		</>
	</Box>
}
Example #6
Source File: connected-status.tsx    From example with MIT License 6 votes vote down vote up
export function ConnectedStatus({ state }: IConnectedStatusProps) {
	const {environment} = useContext(EnvironmentContext)
	return (
		<Stack direction="row" alignItems="center" spacing={2}>
			<Tooltip title="SDK Connection Environment" placement="bottom">
				<Chip
					size="small"
					color="info"
					label={getEnvironmentName(environment)}
					sx={{
						lineHeight: 1.1,
						height: "20px",
						fontSize: "0.8125rem"
					}}
				/>
			</Tooltip>
			<Box sx={{ display: "inline" }}>
				<Typography variant="subtitle1" >Connected </Typography>
				<Typography variant="subtitle2">
					<Address address={state.connection.address}/>
				</Typography>
			</Box>
			<IconButton
				color="inherit"
				title="Disconnect"
				onClick={state.disconnect}
			>
				<Icon icon={faLinkSlash}/>
			</IconButton>
		</Stack>
	)
}
Example #7
Source File: transaction-info.tsx    From example with MIT License 6 votes vote down vote up
export function TransactionPending({ transaction }: ITransactionInfoProps) {
	const [state, setState] = useState<"resolve" | "reject" | "pending">("pending")
	useEffect( () => {
		transaction.wait()
			.then(() => setState("resolve"))
			.catch(() => setState("reject"))
	}, [transaction])

	return <Box sx={{ my: 1 }}>
		<>
			{ state === "pending" && <><CircularProgress size={14}/> Processing</> }
			{ state === "resolve" && <Chip
				label="Confirmed"
				icon={<Icon icon={faCheckDouble}/>}
				variant="outlined"
				color="success"
				size="small"
			/> }
			{ state === "reject" && <Chip
				label="Rejected"
                icon={<Icon icon={faTimes}/>}
                variant="outlined"
                color="error"
				size="small"
			/> }
		</>
	</Box>
}
Example #8
Source File: WeaponCardNano.tsx    From genshin-optimizer with MIT License 6 votes vote down vote up
export default function WeaponCardNano({ weaponId, showLocation = false, onClick, BGComponent = CardDark, }: Data) {
  const weapon = useWeapon(weaponId)
  const weaponSheet = usePromise(weapon?.key && WeaponSheet.get(weapon.key), [weapon?.key])
  const actionWrapperFunc = useCallback(children => <CardActionArea sx={{ height: "100%" }} onClick={onClick}>{children}</CardActionArea>, [onClick],)
  const UIData = useMemo(() => weaponSheet && weapon && computeUIData([weaponSheet.data, dataObjForWeapon(weapon)]), [weaponSheet, weapon])
  if (!weapon || !weaponSheet || !UIData) return <BGComponent sx={{ height: "100%" }}><Skeleton variant="rectangular" sx={{ width: "100%", height: "100%" }} /></BGComponent>;
  const { refinement, location } = weapon
  return <BGComponent sx={{ height: "100%" }}><ConditionalWrapper condition={!!onClick} wrapper={actionWrapperFunc}  >
    <Box display="flex" height="100%" alignItems="stretch" >
      <Box className={`grad-${weaponSheet.rarity}star`} sx={{ height: "100%", position: "relative", flexGrow: 1, display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "flex-end" }} >
        <WeaponNameTooltip sheet={weaponSheet}>
          <Box
            component="img"
            src={weaponSheet.img}
            sx={{ mx: -1, maxHeight: "100%", maxWidth: "100%" }}
          />
        </WeaponNameTooltip>
        <Box sx={{ position: "absolute", width: "100%", height: "100%", p: 0.5, opacity: 0.85, display: "flex", justifyContent: "space-between", pointerEvents: "none" }} >
          <Chip size="small" label={<strong>{WeaponSheet.getLevelString(weapon)}</strong>} color="primary" />
          {showLocation && <Chip size="small" label={<LocationIcon location={location} />} color={"secondary"} sx={{
            overflow: "visible", ".MuiChip-label": {
              overflow: "visible"
            }
          }} />}
        </Box>
        <Box sx={{ position: "absolute", width: "100%", height: "100%", p: 0.5, opacity: 0.85, display: "flex", justifyContent: "flex-end", alignItems: "flex-end" }} >
          {weaponSheet.hasRefinement && <Chip size="small" color="info" label={<strong>R{refinement}</strong>} />}
        </Box>
      </Box>
      <Box display="flex" flexDirection="column" sx={{ p: 1, }}>
        <WeaponStat node={UIData.get(input.weapon.main)} />
        <WeaponStat node={UIData.get(input.weapon.sub)} />
      </Box>
    </Box>
  </ConditionalWrapper></BGComponent >
}
Example #9
Source File: ArtifactCardNano.tsx    From genshin-optimizer with MIT License 5 votes vote down vote up
export default function ArtifactCardNano({ artifactId, slotKey: pSlotKey, mainStatAssumptionLevel = 0, showLocation = false, onClick, BGComponent = CardDark }: Data) {
  const art = useArtifact(artifactId)
  const sheet = usePromise(ArtifactSheet.get(art?.setKey), [art])
  const actionWrapperFunc = useCallback(children => <CardActionArea onClick={onClick} sx={{ height: "100%" }}>{children}</CardActionArea>, [onClick],)
  const theme = useTheme()
  if (!art) return <BGComponent sx={{ display: "flex", height: "100%", alignItems: "center", justifyContent: "center" }}>
    <Box component="img" src={Assets.slot[pSlotKey]} sx={{ width: "25%", height: "auto", opacity: 0.7 }} />
  </BGComponent>

  const { slotKey, rarity, level, mainStatKey, substats, location } = art
  const mainStatLevel = Math.max(Math.min(mainStatAssumptionLevel, rarity * 4), level)
  const mainStatUnit = KeyMap.unit(mainStatKey)
  const levelVariant = "roll" + (Math.floor(Math.max(level, 0) / 4) + 1)
  const element = allElementsWithPhy.find(ele => art.mainStatKey.includes(ele))
  const color = element ? alpha(theme.palette[element].main, 0.6) : alpha(theme.palette.secondary.main, 0.6)
  return <BGComponent sx={{ height: "100%" }}><ConditionalWrapper condition={!!onClick} wrapper={actionWrapperFunc}  >
    <Box display="flex" height="100%">
      <Box className={`grad-${rarity}star`} sx={{ position: "relative", flexGrow: 1, display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center" }} >
        <ArtifactSetSlotTooltip slotKey={slotKey} sheet={sheet}>
          <Box
            component="img"
            src={sheet?.slotIcons[slotKey] ?? ""}
            sx={{ m: -1, maxHeight: "110%", maxWidth: "110%" }}
          />
        </ArtifactSetSlotTooltip>
        <Box sx={{ position: "absolute", width: "100%", height: "100%", p: 0.5, opacity: 0.85, display: "flex", justifyContent: "space-between", pointerEvents: "none" }} >
          <Chip size="small" label={<strong>{` +${level}`}</strong>} color={levelVariant as any} />
          {showLocation && <Chip size="small" label={<LocationIcon location={location} />} color={"secondary"} sx={{
            overflow: "visible", ".MuiChip-label": {
              overflow: "visible"
            }
          }} />}
        </Box>
        {/* mainstats */}
        <Chip size="small" sx={{ position: "absolute", bottom: 0, mb: 1, backgroundColor: color }}
          label={<Typography variant="h6" sx={{ display: "flex", gap: 1, px: 1, zIndex: 1 }}>
            <BootstrapTooltip placement="top" title={<Typography>{KeyMap.getArtStr(mainStatKey)}</Typography>} disableInteractive>
              <span>{element ? uncoloredEleIcons[element] : StatIcon[mainStatKey]}</span>
            </BootstrapTooltip>
            <ColorText color={mainStatLevel !== level ? "warning" : undefined}>{cacheValueString(Artifact.mainStatValue(mainStatKey, rarity, mainStatLevel) ?? 0, KeyMap.unit(mainStatKey))}{mainStatUnit}</ColorText>
          </Typography>} />
      </Box>
      {/* substats */}
      <Box display="flex" flexDirection="column" justifyContent="space-between" sx={{ p: 1, }}>
        {substats.map((stat: ICachedSubstat, i: number) => <SubstatDisplay key={i + stat.key} stat={stat} />)}
      </Box>
    </Box>
  </ConditionalWrapper></BGComponent >
}
Example #10
Source File: index.tsx    From Search-Next with GNU General Public License v3.0 5 votes vote down vote up
Lab: React.FC<PageProps> = (props) => {
  const { route } = props;
  const history = useNavigate();
  const location = useLocation();
  const [list, setList] = React.useState<Router[]>([]);

  React.useEffect(() => {
    setList(route?.routes || []);
  }, []);

  return (
    <div {...props}>
      <Alert severity="info">
        <AlertTitle>提示</AlertTitle>
        实验室中的功能均处在开发中,不保证实际发布。
      </Alert>
      <ContentList>
        {list
          .filter((i) =>
            i?.status && ['beta', 'process'].includes(i?.status)
              ? isBeta()
              : true,
          )
          .map((i) => (
            <ItemCard
              key={i.path}
              title={
                <div className="flex items-center gap-1">
                  {i.title}
                  {i?.status === 'process' && (
                    <Chip
                      color="warning"
                      label={i?.status}
                      size="small"
                      variant="outlined"
                    />
                  )}
                </div>
              }
              icon={i.icon}
              onClick={() => history(i.path)}
            ></ItemCard>
          ))}
      </ContentList>
    </div>
  );
}
Example #11
Source File: EnemyEditor.tsx    From genshin-optimizer with MIT License 5 votes vote down vote up
export function EnemyExpandCard() {
  const { data } = useContext(DataContext)
  const [expanded, setexpanded] = useState(false)
  const toggle = useCallback(() => setexpanded(!expanded), [setexpanded, expanded])
  const eLvlNode = data.get(input.enemy.level)
  const eDefRed = data.get(input.enemy.defRed)
  const eDefIgn = data.get(input.enemy.defIgn)
  return <CardLight>
    <CardContent>
      <Grid container>
        <Grid item flexGrow={1} alignItems="center">
          <Grid container spacing={1}>
            <Grid item>
              <Chip size="small" color="success" label={<span>{KeyMap.get(eLvlNode.info.key)} <strong>{eLvlNode.value}</strong></span>} />
            </Grid>
            {allElementsWithPhy.map(element => <Grid item key={element}>
              <Typography key={element} ><EnemyResText element={element} /></Typography>
            </Grid>)}
            <Grid item>
              <Typography>DEF Reduction {valueString(eDefRed.value, eDefRed.unit)}</Typography>
            </Grid>
            <Grid item>
              <Typography>DEF Ignore {valueString(eDefIgn.value, eDefIgn.unit)}</Typography>
            </Grid>
          </Grid>
        </Grid>
        <Grid item>
          <ExpandButton
            expand={expanded}
            onClick={toggle}
            aria-expanded={expanded}
            aria-label="show more"
            size="small"
            sx={{ p: 0 }}
          >
            <ExpandMore />
          </ExpandButton>
        </Grid>
      </Grid>
    </CardContent>
    <Collapse in={expanded} timeout="auto" unmountOnExit>
      <CardContent sx={{ pt: 0 }}>
        <EnemyEditor />
      </CardContent>
    </Collapse>
  </CardLight>
}
Example #12
Source File: CharacterCard.tsx    From genshin-optimizer with MIT License 5 votes vote down vote up
function Header({ onClick }: { onClick?: (characterKey: CharacterKey, tab: string) => void }) {
  const { data, characterSheet } = useContext(DataContext)
  const characterKey = data.get(input.charKey).value as CharacterKey
  const characterEle = data.get(input.charEle).value as ElementKey
  const characterLevel = data.get(input.lvl).value
  const constellation = data.get(input.constellation).value
  const ascension = data.get(input.asc).value
  const autoBoost = data.get(input.bonus.auto).value
  const skillBoost = data.get(input.bonus.skill).value
  const burstBoost = data.get(input.bonus.burst).value

  const tAuto = data.get(input.total.auto).value
  const tSkill = data.get(input.total.skill).value
  const tBurst = data.get(input.total.burst).value

  const actionWrapperFunc = useCallback(
    children => <CardActionArea onClick={() => characterKey && onClick?.(characterKey, "overview")} sx={{ flexGrow: 1, display: "flex", flexDirection: "column" }}>{children}</CardActionArea>,
    [onClick, characterKey],
  )
  return <ConditionalWrapper condition={!!onClick} wrapper={actionWrapperFunc} >
    <Box display="flex"
      position="relative"
      className={`grad-${characterSheet.rarity}star`}
      sx={{
        "&::before": {
          content: '""',
          display: "block", position: "absolute",
          left: 0, top: 0,
          width: "100%", height: "100%",
          opacity: 0.7,
          backgroundImage: `url(${characterSheet.bannerImg})`, backgroundPosition: "center", backgroundSize: "cover",
        }
      }}
      width="100%" >
      <Box flexShrink={1} sx={{ maxWidth: { xs: "40%", lg: "40%" } }} alignSelf="flex-end" display="flex" flexDirection="column" zIndex={1}>
        <Box
          component="img"
          src={characterSheet.thumbImg}
          width="100%"
          height="auto"
          maxWidth={256}
          sx={{ mt: "auto" }}
        />
      </Box>
      <Box flexGrow={1} sx={{ py: 1, pr: 1 }} display="flex" flexDirection="column" zIndex={1}>
        <Chip label={<Typography variant="subtitle1">{characterSheet.name}</Typography>} size="small" color={characterEle} sx={{ opacity: 0.85 }} />
        <Grid container spacing={1} flexWrap="nowrap">
          <Grid item sx={{ textShadow: "0 0 5px gray" }}>
            <Typography component="span" variant="h6" whiteSpace="nowrap" >Lv. {characterLevel}</Typography>
            <Typography component="span" variant="h6" color="text.secondary">/{ascensionMaxLevel[ascension]}</Typography>
          </Grid>
          <Grid item>
            <Typography variant="h6"><SqBadge>C{constellation}</SqBadge></Typography>
          </Grid>
        </Grid>
        <Grid container spacing={1} flexWrap="nowrap">
          <Grid item>
            <Chip color={autoBoost ? "info" : "secondary"} label={<strong >{tAuto}</strong>} />
          </Grid>
          <Grid item>
            <Chip color={skillBoost ? "info" : "secondary"} label={<strong >{tSkill}</strong>} />
          </Grid>
          <Grid item>
            <Chip color={burstBoost ? "info" : "secondary"} label={<strong >{tBurst}</strong>} />
          </Grid>
        </Grid>
        <Typography mt={1} ><Stars stars={characterSheet.rarity} colored /></Typography>
      </Box>
    </Box>
  </ConditionalWrapper>
}
Example #13
Source File: CategoriesList.tsx    From fluttertemplates.dev with MIT License 5 votes vote down vote up
export default function CategoriesList(props: CategoriesListProps) {
  return (
    <div className="categories-list">
      {props.showAll && (
        <Chip
          label="#all"
          component="a"
          color={props.selected === "all" ? "secondary" : "default"}
          variant={props.selected === "all" ? "filled" : "outlined"}
          clickable
          style={{
            margin: "4px",
          }}
          key="all"
          onClick={() => {
            if (props.onChange) {
              props.onChange("all");
            }
          }}
        />
      )}
      {props.categories.map((val) => {
        return (
          <Chip
            label={`#${val}`}
            component="a"
            color={
              props.selected.toLowerCase() === val.toLowerCase()
                ? "secondary"
                : "default"
            }
            variant={
              props.selected.toLowerCase() === val.toLowerCase()
                ? "filled"
                : "outlined"
            }
            clickable
            style={{
              margin: "4px",
            }}
            key={val}
            href={props.onChange ? undefined : `/templates?catId=${val}`}
            onClick={() => {
              if (props.onChange) {
                props.onChange(val);
              }
            }}
          />
        );
      })}
    </div>
  );
}
Example #14
Source File: AccountTransactionListEntry.tsx    From abrechnung with GNU Affero General Public License v3.0 5 votes vote down vote up
export default function AccountTransactionListEntry({ group, transaction, accountID }) {
    return (
        <ListItemLink to={`/groups/${group.id}/transactions/${transaction.id}`}>
            <ListItemAvatar sx={{ minWidth: { xs: "40px", md: "56px" } }}>
                {transaction.type === "purchase" ? (
                    <Tooltip title="Purchase">
                        <PurchaseIcon color="primary" />
                    </Tooltip>
                ) : transaction.type === "transfer" ? (
                    <Tooltip title="Money Transfer">
                        <TransferIcon color="primary" />
                    </Tooltip>
                ) : (
                    <Tooltip title="Unknown Transaction Type">
                        <HelpOutline color="primary" />
                    </Tooltip>
                )}
            </ListItemAvatar>
            <ListItemText
                primary={
                    <>
                        {transaction.is_wip && (
                            <Chip color="info" variant="outlined" label="WIP" size="small" sx={{ mr: 3 }} />
                        )}
                        <Typography variant="body1" component="span">
                            {transaction.description}
                        </Typography>
                    </>
                }
                secondary={DateTime.fromISO(transaction.billed_at).toLocaleString(DateTime.DATE_FULL)}
            />
            <ListItemText>
                <Typography align="right" variant="body2">
                    <Typography
                        component="span"
                        sx={{ color: (theme) => balanceColor(transaction.account_balances[accountID].total, theme) }}
                    >
                        {transaction.account_balances[accountID].total.toFixed(2)} {group.currency_symbol}
                    </Typography>
                    <br />
                    <Typography component="span" sx={{ typography: "body2", color: "text.secondary" }}>
                        last changed:{" "}
                        {DateTime.fromISO(transaction.last_changed).toLocaleString(DateTime.DATETIME_FULL)}
                    </Typography>
                </Typography>
            </ListItemText>
        </ListItemLink>
    );
}
Example #15
Source File: DetailCreate.tsx    From airmessage-web with Apache License 2.0 5 votes vote down vote up
function SelectionChip(props: {selection: SelectionData, label: string, tooltip?: string, onDelete: () => void, className?: string}) {
	const chip = <Chip className={props.className} label={props.label} avatar={<Avatar src={props.selection.avatar} alt={props.selection.name} />} onDelete={props.onDelete} />;
	return !props.tooltip ? chip : <Tooltip title={props.tooltip}>{chip}</Tooltip>;
}
Example #16
Source File: TransactionListEntry.tsx    From abrechnung with GNU Affero General Public License v3.0 5 votes vote down vote up
export function TransactionListEntry({ group, transaction }) {
    const accounts = useRecoilValue(accountsSeenByUser(group.id));
    const accountNamesFromShares = (shares) => {
        return shares.map((s) => accounts.find((a) => a.id === parseInt(s))?.name).join(", ");
    };

    return (
        <>
            <ListItemLink to={`/groups/${group.id}/transactions/${transaction.id}`}>
                <ListItemAvatar sx={{ minWidth: { xs: "40px", md: "56px" } }}>
                    {transaction.type === "purchase" ? (
                        <Tooltip title="Purchase">
                            <PurchaseIcon color="primary" />
                        </Tooltip>
                    ) : transaction.type === "transfer" ? (
                        <Tooltip title="Money Transfer">
                            <TransferIcon color="primary" />
                        </Tooltip>
                    ) : (
                        <Tooltip title="Unknown Transaction Type">
                            <HelpOutline color="primary" />
                        </Tooltip>
                    )}
                </ListItemAvatar>
                <ListItemText
                    primary={
                        <>
                            {transaction.is_wip && (
                                <Chip color="info" variant="outlined" label="WIP" size="small" sx={{ mr: 1 }} />
                            )}
                            <Typography variant="body1" component="span">
                                {transaction.description}
                            </Typography>
                        </>
                    }
                    secondary={
                        <>
                            <Typography variant="body2" component="span" sx={{ color: "text.primary" }}>
                                by {accountNamesFromShares(Object.keys(transaction.creditor_shares))}, for{" "}
                                {accountNamesFromShares(Object.keys(transaction.debitor_shares))}
                            </Typography>
                            <br />
                            {DateTime.fromISO(transaction.billed_at).toLocaleString(DateTime.DATE_FULL)}
                        </>
                    }
                />
                <ListItemText>
                    <Typography align="right" variant="body2">
                        {transaction.value.toFixed(2)} {transaction.currency_symbol}
                        <br />
                        <Typography component="span" sx={{ typography: "body2", color: "text.secondary" }}>
                            last changed:{" "}
                            {DateTime.fromISO(transaction.last_changed).toLocaleString(DateTime.DATETIME_FULL)}
                        </Typography>
                    </Typography>
                </ListItemText>
            </ListItemLink>
            <Divider sx={{ display: { lg: "none" } }} component="li" />
        </>
    );
}
Example #17
Source File: index.tsx    From Search-Next with GNU General Public License v3.0 4 votes vote down vote up
OtherApis: React.FC<PageProps> = (props) => {
  const { route, children } = props;
  const [iconApi, setIconApi] = React.useState('');
  const [apiStatus, setApiStatus] = React.useState<ApiStatus>({});

  const init = () => {
    const account = localStorage.getItem('account');
    const data = getOtherIconApi({
      userId: account ?? '',
      type: 'icon',
    });
    setIconApi(data.apiId);
    let map = {} as ApiStatus;
    websiteIconApis.forEach((i) => {
      map[i.id] = 'warning';
    });
    setApiStatus(map);
  };

  const onChange = (event: SelectChangeEvent<any>) => {
    const select = event.target.value;
    setIconApi(select);
    const account = localStorage.getItem('account');
    setOtherIconApi({
      userId: account ?? '',
      apiId: select,
      type: 'icon',
    });
  };

  const StatusChip = (status: string) => {
    const statusMap = {
      warning: (
        <>
          <PendingOutlined /> 等待响应
        </>
      ),
      success: (
        <>
          <Done /> 成功
        </>
      ),
      error: (
        <>
          <Close /> 失败
        </>
      ),
    };
    return (
      <Chip
        size="small"
        color={status as any}
        label={
          <div className="text-sm flex items-center gap-1">
            {(statusMap as any)[status as any]}
          </div>
        }
      />
    );
  };

  React.useEffect(() => {
    init();
  }, []);

  return (
    <div>
      <ContentList>
        <Alert severity="info">
          <AlertTitle>提示</AlertTitle>
          不同地区,不同网络下各API的表现可能不同,请选择最适合的API以提高使用体验。
        </Alert>
        <ItemAccordion
          title="Website Icon API"
          desc="设置获取网站图标的api"
          action={
            <Select
              label="API"
              value={iconApi}
              size="small"
              onChange={onChange}
              options={websiteIconApis.map((i) => ({
                label: i.name,
                value: i.id,
              }))}
            />
          }
        >
          <div className="flex items-center text-sm gap-1 pb-2">
            <PendingOutlined /> <span>等待响应</span>
            <Done /> <span>成功</span>
            <Close /> <span>失败</span> 状态仅作参考,具体以实际使用为准
          </div>
          {websiteIconApis.map((i) => {
            return (
              <AccordionDetailItem
                key={i.id}
                disabledRightPadding
                title={i.name}
                action={
                  <>
                    {StatusChip(apiStatus[i.id])}
                    <img
                      className={css`
                        display: none;
                      `}
                      src={`${i.url}google.com`}
                      alt={i.name}
                      onLoad={(v) => {
                        setApiStatus({ ...apiStatus, [i.id]: 'success' });
                      }}
                      onError={(err) => {
                        setApiStatus({ ...apiStatus, [i.id]: 'error' });
                      }}
                    />
                  </>
                }
              />
            );
          })}
        </ItemAccordion>
      </ContentList>
    </div>
  );
}
Example #18
Source File: Filter.tsx    From Cromwell with MIT License 4 votes vote down vote up
render() {
    const { instanceSettings } = this.props;
    const { pluginSettings } = this.props.data ?? {};
    const { attributes, productCategory } = this.initialData ?? {};
    const { isMobileOpen, minPrice, maxPrice, collapsedItems, isLoading } = this.state;
    instanceSettings?.getInstance?.(this);

    if (isLoading) return (
      <div style={{ width: '100%', height: '100px' }}>
        <LoadBox size={60} />
      </div>
    );

    const isMobile = !instanceSettings?.disableMobile && this.props.isMobile;
    const pcCollapsedByDefault = pluginSettings?.collapsedByDefault ?? defaultSettings.collapsedByDefault
    const mobileCollapsedByDefault = pluginSettings?.mobileCollapsedByDefault ?? defaultSettings.mobileCollapsedByDefault;
    const _collapsedByDefault = isMobile ? mobileCollapsedByDefault : pcCollapsedByDefault;
    const productListId = instanceSettings?.listId ?? pluginSettings?.listId;

    if (this.collapsedByDefault !== _collapsedByDefault) {
      this.collapsedByDefault = _collapsedByDefault;
      this.setState({ collapsedItems: {} });
    }

    setListProps(productListId, productCategory, this.client, this.getFilterParams(), this.updateFilterMeta);

    const filterContent = (
      <div>
        {isMobile && (
          <div className="productFilter_mobileHeader">
            <p>Filter</p>
            <IconButton
              aria-label="Close filter"
              className="productFilter_mobileCloseBtn"
              onClick={this.handleMobileClose}>
              <CloseIcon />
            </IconButton>
          </div>
        )}
        {this.getFilterItem({
          title: 'Search',
          key: 'search',
          content: (
            <TextField
              style={{
                padding: '0 15px 15px 15px',
                width: '100%',
              }}
              placeholder="type to search..."
              variant="standard"
              onChange={e => this.onSearchChange(e.target.value)}
            />
          )
        })}
        {productCategory &&
          !!(productCategory.parent || productCategory.children?.length) &&
          this.getFilterItem({
            title: 'Categories',
            key: 'categories',
            content: (
              <div className={clsx('productFilter_categoryBox',
                'productFilter_styledScrollBar',
                'productFilter_list')}>
                {productCategory.parent && (
                  <Chip className="productFilter_category"
                    label={productCategory.parent.name}
                    onClick={this.handleCategoryClick(productCategory.parent)} />
                )}
                {productCategory && (
                  <Chip className="productFilter_category"
                    variant="outlined"
                    disabled
                    style={{ marginLeft: productCategory.parent ? '15px' : '' }}
                    label={productCategory.name}
                    onClick={this.handleCategoryClick(productCategory)} />
                )}
                {!!productCategory.children?.length && (
                  <>
                    {productCategory.children.map(child => (
                      <Chip key={child.id}
                        className="productFilter_category"
                        style={{ marginLeft: ((productCategory?.parent ? 15 : 0) + 15) + 'px' }}
                        label={child.name}
                        onClick={this.handleCategoryClick(child)} />
                    ))}
                  </>
                )}
              </div>
            )
          })}
        {this.getFilterItem({
          title: 'Price',
          key: 'price',
          content: (
            <Slider
              onChange={this.onPriceRangeChange}
              minPrice={minPrice}
              maxPrice={maxPrice}
            />
          )
        })}
        {attributes && (
          attributes.map(attr => {
            if (!attr.key || !attr.values) return null;
            const checked: string[] | undefined = this.checkedAttrs[attr.key];
            const numberOfChecked = () => checked ? checked.length : 0;
            const handleToggleAll = () => {
              if (attr.key && attr.values && attr.values?.length !== 0) {
                if (numberOfChecked() === attr.values?.length) {
                  this.handleSetAttribute(attr.key, [])
                } else {
                  this.handleSetAttribute(attr.key, attr.values.map(v => v.value))
                }
              }
            }
            if (collapsedItems[attr.key] === undefined) {
              collapsedItems[attr.key] = this.collapsedByDefault;
            }
            const isExpanded = !collapsedItems[attr.key];
            return (
              <Card key={attr.key} className="productFilter_card">
                <div className="productFilter_headerWrapper">
                  <CardHeader
                    className="productFilter_cardHeader"
                    avatar={
                      <Checkbox
                        color="primary"
                        onClick={handleToggleAll}
                        checked={numberOfChecked() === attr.values.length && attr.values.length !== 0}
                        indeterminate={numberOfChecked() !== attr.values.length && numberOfChecked() !== 0}
                        disabled={attr.values.length === 0}
                        inputProps={{ 'aria-label': 'all items selected' }}
                      />
                    }
                    title={attr.key}
                    subheader={`${numberOfChecked()}/${attr.values.length} selected`}
                  />
                  <IconButton
                    onClick={() => this.setState(prev => ({
                      collapsedItems: {
                        ...prev.collapsedItems,
                        [attr.key!]: !prev.collapsedItems[attr.key!]
                      }
                    }))}
                    className={clsx('productFilter_expand', {
                      'productFilter_expandOpen': isExpanded,
                    })}
                    aria-label="Toggle filter visibility"
                    aria-expanded={isExpanded}
                  >
                    <ExpandMoreIcon />
                  </IconButton>
                </div>
                <Collapse in={isExpanded} timeout="auto" unmountOnExit>
                  <Divider />
                  <List className={clsx('productFilter_list', 'productFilter_styledScrollBar')} dense component="div" role="list">
                    {attr.values.map((attrValue: TAttributeValue) => {
                      const value = attrValue.value
                      const labelId = `attribute-list-${attr.key}-${value}-label`;
                      return (
                        <ListItem key={value} role="listitem" button
                          onClick={() => {
                            const newChecked = checked ? [...checked] : [];
                            const currentIndex = newChecked.indexOf(value);
                            if (currentIndex === -1) {
                              newChecked.push(value);
                            } else {
                              newChecked.splice(currentIndex, 1);
                            }
                            this.handleSetAttribute(attr.key!, newChecked);
                          }}>
                          <ListItemIcon>
                            <Checkbox
                              color="primary"
                              checked={checked ? checked.indexOf(value) !== -1 : false}
                              tabIndex={-1}
                              disableRipple
                              inputProps={{ 'aria-labelledby': labelId }}
                            />
                          </ListItemIcon>
                          {attrValue.icon && (
                            <div
                              style={{ backgroundImage: `url(${attrValue.icon}` }}
                              className="productFilter_attrValueIcon"></div>
                          )}
                          <ListItemText id={labelId} primary={value} />
                        </ListItem>
                      );
                    })}
                    <ListItem />
                  </List>
                </Collapse>
              </Card>
            )
          })
        )}
      </div>
    );

    if (isMobile) {
      const onOpen = () => {

      }
      const mobileIconPosition = pluginSettings?.mobileIconPosition ?? defaultSettings.mobileIconPosition;

      return (
        <div>
          <IconButton
            aria-label="Open product filter"
            className="productFilter_mobileOpenBtn"
            style={{
              top: mobileIconPosition.top + 'px',
              left: mobileIconPosition.left + 'px'
            }}
            onClick={this.handleMobileOpen}>
            <FilterListIcon />
          </IconButton>
          <SwipeableDrawer
            open={isMobileOpen}
            onClose={this.handleMobileClose}
            onOpen={onOpen}
            classes={{ paper: 'productFilter_styledScrollBar' }}
          >
            <div className="productFilter_drawer">
              {filterContent}
            </div>
          </SwipeableDrawer>
        </div>
      );
    }

    return filterContent;
  }
Example #19
Source File: index.tsx    From Search-Next with GNU General Public License v3.0 4 votes vote down vote up
SettingPage: React.FC<SettingPageProps> = ({
  children,
  route,
  ...props
}) => {
  const history = useNavigate();
  const location = useLocation();
  const params = useParams();
  const [menuList, setMenuList] = React.useState<Router[] | undefined>([]);
  const [breads, setBreads] = React.useState<Router[]>([]);

  const getBreadCrumbs = (routes: Router[], parentPath: string = '') => {
    let breadCrumbs: Router[] = [];

    const findRoute = (
      routes: Router[] | undefined,
      parentPath: string = '',
    ) => {
      if (!routes) return routes;
      const loc = location;
      routes.forEach((i) => {
        const fullPath = `${parentPath}/${i.path}`;
        const splitPath = fullPath.split(':');
        const paramsPath =
          splitPath[0] +
          Object.values(params)
            .filter((u) => u !== '')
            .join('/');
        if (
          loc.pathname.indexOf(i.path) !== -1 ||
          loc.pathname === paramsPath
        ) {
          breadCrumbs.push(i);
        }
        if (i.routes) {
          findRoute(i.routes, fullPath);
        }
      });
    };
    findRoute(routes, parentPath);

    return breadCrumbs;
  };

  React.useEffect(() => {
    if (location.pathname === '/setting') {
      history(route?.routes?.[0].path || '/setting', { replace: true });
    }
    setMenuList(route?.routes);
  }, []);

  React.useEffect(() => {
    const breads = getBreadCrumbs(route.routes || [], '/setting');
    setBreads(breads);
  }, [location]);

  return (
    <div className="flex flex-row h-screen bg-gray-70">
      <div className="w-72 p-4 h-full">
        <div className="flex gap-1">
          <Tooltip title="回到首页" arrow>
            <IconButton
              size="small"
              onClick={() => {
                history('/');
              }}
            >
              <Home />
            </IconButton>
          </Tooltip>
          <Tooltip title="返回上级" arrow>
            <IconButton
              size="small"
              onClick={() => {
                history(-1);
              }}
            >
              <KeyboardBackspace />
            </IconButton>
          </Tooltip>
        </div>
        <div className="flex flex-col gap-1 my-4">
          {menuList?.map((i) => (
            <div
              key={i.path}
              className={classNames(
                'hover:bg-gray-150',
                'transition-all',
                'px-2.5',
                'py-1.5',
                'cursor-pointer',
                'rounded',
                'text-sm',
                'text-gray-800',
                {
                  'bg-gray-150': location.pathname.indexOf(i.path) > -1,
                },
              )}
              onClick={() => {
                history(i.path);
              }}
            >
              {i.title}
            </div>
          ))}
        </div>
      </div>
      <div className="h-full overflow-hidden flex flex-col w-full px-6 py-4">
        <Breadcrumbs separator="›" aria-label="breadcrumb" className="mb-4">
          {breads.map((i, index) => (
            <div
              className={classNames('text-2xl cursor-pointer mb-0', {
                'font-semibold': index === breads.length - 1,
              })}
              key={i.path}
              onClick={() => {
                const path =
                  '/setting/' +
                  breads
                    .map((i) => i.path)
                    .filter((_, ji) => ji <= index)
                    .join('/');
                index !== breads.length - 1 ? history(path) : null;
              }}
            >
              <div className="flex items-center gap-1">
                {i.title}
                {i?.status === 'process' && (
                  <Chip
                    color="warning"
                    label={i?.status}
                    size="small"
                    variant="outlined"
                  />
                )}
              </div>
            </div>
          ))}
        </Breadcrumbs>
        <div className="flex-grow overflow-y-auto w-full">
          <div className="max-w-4xl">
            <Outlet />
          </div>
        </div>
        <div className="text-center max-w-4xl">
          <Copyright />
        </div>
      </div>
    </div>
  );
}
Example #20
Source File: engineSelectPopper.tsx    From Search-Next with GNU General Public License v3.0 4 votes vote down vote up
EngineSelectPopper: FC<EngineSelectPopperProps> = (props) => {
  const {
    width = 300,
    anchorEl,
    open = false,
    onBtnClick,
    onEngineSelect,
    engine,
  } = props;
  const [classifyEngineList, setClassifyEngineList] = useState<
    SearchEngineClassifyWithChildren[]
  >([]);
  const [selected, setSelected] = useState<string>('');
  const [engineList, setEngineList] = React.useState([] as SearchEngine[]);

  const getClassifyEngine = () => {
    getClassifyEngineListApi().then((res) => {
      const current = {
        ...engine.engine,
        classifyId: engine.engine?.classify?._id,
      } as SearchEngine;
      let filterEngines = res
        .map((i) => i.children)
        .flat()
        .filter((u) => u._id !== engine.engine?._id)
        .slice(0, engine.indexCount - 1);
      if (engine.sortType === 'count') {
        filterEngines = filterEngines.sort((r, t) => t.count - r.count);
      }
      setEngineList([current, ...filterEngines]);
      setClassifyEngineList(res);
      res.length > 0 && setSelected(res[0]._id);
    });
  };

  useEffect(() => {
    if (engine?.engine?.classify?._id) {
      setSelected(engine?.engine?.classify?._id);
    }
    getClassifyEngine();
  }, [engine]);

  return (
    <div className="mb-1">
      <Popper open={open} anchorEl={anchorEl} placement="top">
        {({ TransitionProps }) => (
          <Card
            {...TransitionProps}
            style={{ width: `${anchorEl?.clientWidth}px` }}
            className="mb-1"
          >
            <div className="p-2 flex gap-2 items-start">
              <div className="max-h-20 overflow-y-auto pr-1">
                {classifyEngineList.map((item) => (
                  <div
                    key={item._id}
                    onClick={() => {
                      setSelected(item._id);
                    }}
                    className={classnames(
                      'px-1.5 py-0.5 cursor-pointer rounded text-sm',
                      selected === item._id
                        ? 'bg-primary text-white'
                        : 'bg-white',
                    )}
                  >
                    {item.name}
                  </div>
                ))}
              </div>
              <div className="flex gap-1 items-start justify-start flex-grow">
                {classifyEngineList
                  .filter((i) => i._id === selected)?.[0]
                  ?.children.map((i) => (
                    <div
                      className={classnames(
                        'px-1.5 py-0.5 cursor-pointer rounded text-sm',
                        engine?.engine?._id === i._id
                          ? 'bg-primary text-white'
                          : 'bg-white border',
                      )}
                      onClick={() => {
                        onEngineSelect(i);
                      }}
                    >
                      {i.name}
                    </div>
                  ))}
              </div>
              <IconButton
                onClick={() => {
                  onBtnClick(false);
                }}
                size="small"
              >
                <Close />
              </IconButton>
            </div>
          </Card>
        )}
      </Popper>
      <div className="w-full text-left mb-1 flex justify-start items-center overflow-x-auto">
        {engineList.map((i, j) => (
          <Chip
            key={j}
            className={classnames(
              'mx-1',
              i._id === engine?.engine?._id
                ? 'bg-primary text-white'
                : 'bg-gray-100',
            )}
            size="small"
            label={i.name}
            onClick={(e) => onEngineSelect(i)}
          ></Chip>
        ))}
        {engine.mode === 'custom' && (
          <Chip
            onClick={(e: any) => {
              onBtnClick(!open);
            }}
            className={classnames('mx-1', 'bg-gray-100')}
            size="small"
            label={
              <div className="text-sm flex gap-1 items-center">
                <Settings className="text-base" />
              </div>
            }
          />
        )}
      </div>
    </div>
  );
}
Example #21
Source File: EditAccountModal.tsx    From abrechnung with GNU Affero General Public License v3.0 4 votes vote down vote up
export default function EditAccountModal({ group, show, onClose, account }) {
    const setAccounts = useSetRecoilState(groupAccounts(group.id));
    const userPermissions = useRecoilValue(currUserPermissions(group.id));
    const currentUser = useRecoilValue(userData);
    const memberIDToUsername = useRecoilValue(groupMemberIDsToUsername(group.id));

    const handleSubmit = (values, { setSubmitting }) => {
        updateAccountDetails({
            accountID: values.accountID,
            name: values.name,
            description: values.description,
            owningUserID: values.owningUserID,
        })
            .then((account) => {
                console.log(account);
                updateAccount(account, setAccounts);
                setSubmitting(false);
                onClose();
            })
            .catch((err) => {
                toast.error(err);
                setSubmitting(false);
            });
    };

    return (
        <Dialog open={show} onClose={onClose}>
            <DialogTitle>Edit Personal Account</DialogTitle>
            <DialogContent>
                <Formik
                    initialValues={{
                        accountID: account?.id,
                        name: account?.name,
                        description: account?.description,
                        owningUserID: account?.owning_user_id,
                    }}
                    onSubmit={handleSubmit}
                    enableReinitialize={true}
                >
                    {({ values, handleChange, handleBlur, handleSubmit, isSubmitting, setFieldValue }) => (
                        <Form>
                            <TextField
                                margin="normal"
                                required
                                fullWidth
                                variant="standard"
                                autoFocus
                                name="name"
                                label="Account Name"
                                value={values.name}
                                onBlur={handleBlur}
                                onChange={handleChange}
                            />
                            <TextField
                                margin="normal"
                                fullWidth
                                variant="standard"
                                name="description"
                                label="Description"
                                value={values.description}
                                onBlur={handleBlur}
                                onChange={handleChange}
                            />
                            {userPermissions.is_owner ? (
                                <GroupMemberSelect
                                    margin="normal"
                                    group={group}
                                    label="Owning user"
                                    value={values.owningUserID}
                                    onChange={(user_id) => setFieldValue("owningUserID", user_id)}
                                />
                            ) : account?.owning_user_id === null || account?.owning_user_id === currentUser.id ? (
                                <FormControlLabel
                                    control={
                                        <Checkbox
                                            name="owningUserID"
                                            onChange={(e) =>
                                                setFieldValue("owningUserID", e.target.checked ? currentUser.id : null)
                                            }
                                            checked={values.owningUserID === currentUser.id}
                                        />
                                    }
                                    label="This is me"
                                />
                            ) : (
                                <span>
                                    Owned by{" "}
                                    <Chip
                                        size="small"
                                        component="span"
                                        color="primary"
                                        label={memberIDToUsername[account?.owning_user_id]}
                                    />
                                </span>
                            )}

                            {isSubmitting && <LinearProgress />}
                            <DialogActions>
                                <Button color="primary" type="submit">
                                    Save
                                </Button>
                                <Button color="error" onClick={onClose}>
                                    Cancel
                                </Button>
                            </DialogActions>
                        </Form>
                    )}
                </Formik>
            </DialogContent>
        </Dialog>
    );
}
Example #22
Source File: FileGallery.tsx    From abrechnung with GNU Affero General Public License v3.0 4 votes vote down vote up
export default function FileGallery({ transaction }) {
    const [files, setFiles] = useState([]); // map of file id to object
    const [active, setActive] = useState(0);
    const setTransactions = useSetRecoilState(groupTransactions(transaction.group_id));

    const [showUploadDialog, setShowUploadDialog] = useState(false);
    const [showImage, setShowImage] = useState(false);

    useEffect(() => {
        const newFileIDs = new Set(transaction.files.map((file) => file.id));
        const filteredFiles = files.reduce((map, file) => {
            map[file.id] = file;
            return map;
        }, {});
        for (const loadedFile of files) {
            if (!newFileIDs.has(loadedFile.id)) {
                URL.revokeObjectURL(loadedFile.objectUrl); // clean up memory
                delete filteredFiles[loadedFile.id];
            }
        }
        setFiles(Object.values(filteredFiles)); // TODO: maybe include placeholders
        setActive(Math.max(0, Math.min(active, transaction.files.length - 1)));

        const newFiles = transaction.files.filter((file) => !filteredFiles.hasOwnProperty(file.id));
        Promise.all(
            newFiles.map((newFile) => {
                return fetchFile({
                    fileID: newFile.id,
                    blobID: newFile.blob_id,
                }).then((resp) => {
                    const objectUrl = URL.createObjectURL(resp.data);
                    return {
                        ...newFile,
                        objectUrl: objectUrl,
                    };
                });
            })
        )
            .then((newlyLoadedFiles) => {
                setFiles([...Object.values(filteredFiles), ...newlyLoadedFiles]);
            })
            .catch((err) => {
                toast.error(`Error loading file: ${err}`);
            });
    }, [transaction]);

    const toNextImage = () => {
        if (active < files.length - 1) {
            setActive(active + 1);
        }
    };

    const toPrevImage = () => {
        if (active > 0) {
            setActive(active - 1);
        }
    };

    const doShowImage = (img) => {
        setShowImage(true);
    };

    const deleteSelectedFile = () => {
        if (active < files.length) {
            // sanity check, should not be needed
            deleteFile({ fileID: files[active].id })
                .then((t) => {
                    updateTransactionInState(t, setTransactions);
                    setShowImage(false);
                })
                .catch((err) => {
                    toast.error(`Error deleting file: ${err}`);
                });
        }
    };

    // @ts-ignore
    return (
        <>
            <Grid
                container
                justifyContent="center"
                alignItems="center"
                style={{
                    position: "relative",
                    height: "200px",
                    width: "100%",
                }}
            >
                {files.length === 0 ? (
                    <img height="100%" src={placeholderImg} alt="placeholder" />
                ) : (
                    files.map((item, idx) => (
                        <Transition key={item.id} in={active === idx} timeout={duration}>
                            {(state) => (
                                <img
                                    height="100%"
                                    style={{
                                        ...defaultStyle,
                                        ...transitionStyles[state],
                                    }}
                                    onClick={() => doShowImage(item)}
                                    src={item.objectUrl}
                                    srcSet={item.objectUrl}
                                    alt={item.filename.split(".")[0]}
                                    loading="lazy"
                                />
                            )}
                        </Transition>
                    ))
                )}
                <Chip
                    sx={{ position: "absolute", top: "5px", right: "10px" }}
                    size="small"
                    label={`${Math.min(files.length, active + 1)} / ${files.length}`}
                />
                {active > 0 && (
                    <IconButton onClick={toPrevImage} sx={{ position: "absolute", top: "40%", left: "10px" }}>
                        <ChevronLeft />
                    </IconButton>
                )}
                {active < files.length - 1 && (
                    <IconButton onClick={toNextImage} sx={{ position: "absolute", top: "40%", right: "10px" }}>
                        <ChevronRight />
                    </IconButton>
                )}
                {transaction.is_wip && (
                    <>
                        <IconButton
                            color="primary"
                            sx={{
                                position: "absolute",
                                top: "80%",
                                right: "10px",
                            }}
                            onClick={() => setShowUploadDialog(true)}
                        >
                            <AddCircle fontSize="large" />
                        </IconButton>
                        <ImageUploadDialog
                            transaction={transaction}
                            show={showUploadDialog}
                            onClose={() => setShowUploadDialog(false)}
                        />
                    </>
                )}
            </Grid>
            <Dialog open={showImage} onClose={() => setShowImage(false)} scroll="body">
                {active < files.length && <DialogTitle>{files[active].filename.split(".")[0]}</DialogTitle>}

                <DialogContent>
                    <Grid
                        container
                        justifyContent="center"
                        alignItems="center"
                        style={{
                            position: "relative",
                        }}
                    >
                        {active < files.length && (
                            <img
                                height="100%"
                                width="100%"
                                src={files[active]?.objectUrl}
                                srcSet={files[active]?.objectUrl}
                                alt={files[active]?.filename}
                                loading="lazy"
                            />
                        )}
                        {active > 0 && (
                            <IconButton
                                onClick={toPrevImage}
                                sx={{
                                    position: "absolute",
                                    top: "40%",
                                    left: "0px",
                                }}
                            >
                                <ChevronLeft />
                            </IconButton>
                        )}
                        {active < files.length - 1 && (
                            <IconButton
                                onClick={toNextImage}
                                sx={{
                                    position: "absolute",
                                    top: "40%",
                                    right: "0px",
                                }}
                            >
                                <ChevronRight />
                            </IconButton>
                        )}
                    </Grid>
                </DialogContent>
                {transaction.is_wip && (
                    <DialogActions>
                        <Button startIcon={<Delete />} onClick={deleteSelectedFile} variant="outlined" color="error">
                            Delete
                        </Button>
                    </DialogActions>
                )}
            </Dialog>
        </>
    );
}
Example #23
Source File: TransactionActions.tsx    From abrechnung with GNU Affero General Public License v3.0 4 votes vote down vote up
export default function TransactionActions({ groupID, transaction }) {
    const [confirmDeleteDialogOpen, setConfirmDeleteDialogOpen] = useState(false);

    const history = useHistory();
    const userPermissions = useRecoilValue(currUserPermissions(groupID));
    const setTransactions = useSetRecoilState(groupTransactions(transaction.group_id));
    const localTransactionChanges = useRecoilValue(pendingTransactionDetailChanges(transaction.id));
    const localPositionChanges = useRecoilValue(pendingTransactionPositionChanges(transaction.id));
    const resetLocalTransactionChanges = useResetRecoilState(pendingTransactionDetailChanges(transaction.id));
    const resetLocalPositionChanges = useResetRecoilState(pendingTransactionPositionChanges(transaction.id));

    const updateTransactionAndClearLocal = useRecoilTransaction_UNSTABLE(
        ({ get, set, reset }) =>
            (transaction: TransactionBackend) => {
                set(groupTransactions(transaction.group_id), (currTransactions) => {
                    return currTransactions.map((t) => (t.id === transaction.id ? transaction : t));
                });
                reset(pendingTransactionDetailChanges(transaction.id));
                reset(pendingTransactionPositionChanges(transaction.id));
            }
    );

    const edit = () => {
        if (!transaction.is_wip) {
            createTransactionChange({
                transactionID: transaction.id,
            })
                .then((t) => {
                    updateTransactionAndClearLocal(t);
                })
                .catch((err) => {
                    toast.error(err);
                });
        }
    };

    const abortEdit = () => {
        if (transaction.is_wip) {
            if (transaction.has_committed_changes) {
                discardTransactionChange({
                    transactionID: transaction.id,
                })
                    .then((t) => {
                        updateTransactionAndClearLocal(t);
                    })
                    .catch((err) => {
                        toast.error(err);
                    });
            } else {
                history.push(`/groups/${groupID}/`);
            }
        }
    };

    const commitEdit = () => {
        if (transaction.is_wip) {
            // update the transaction given the currently pending changes
            // find out which local changes we have and send them to da server
            const positions = Object.values(localPositionChanges.modified)
                .concat(
                    Object.values(localPositionChanges.added).map((position) => ({
                        ...position,
                        id: -1,
                    }))
                )
                .map((p) => ({
                    id: p.id,
                    name: p.name,
                    communist_shares: p.communist_shares,
                    price: p.price,
                    usages: p.usages,
                    deleted: p.deleted,
                }));

            if (Object.keys(localTransactionChanges).length > 0) {
                updateTransaction({
                    transactionID: transaction.id,
                    description: transaction.description,
                    value: transaction.value,
                    billedAt: transaction.billed_at,
                    currencySymbol: transaction.currency_symbol,
                    currencyConversionRate: transaction.currency_conversion_rate,
                    creditorShares: transaction.creditor_shares,
                    debitorShares: transaction.debitor_shares,
                    ...localTransactionChanges,
                    positions: positions.length > 0 ? positions : null,
                })
                    .then((t) => {
                        updateTransactionAndClearLocal(t);
                    })
                    .catch((err) => {
                        toast.error(err);
                    });
            } else if (positions.length > 0) {
                updateTransactionPositions({
                    transactionID: transaction.id,
                    positions: positions,
                })
                    .then((t) => {
                        updateTransactionAndClearLocal(t);
                    })
                    .catch((err) => {
                        toast.error(err);
                    });
            } else {
                commitTransaction({ transactionID: transaction.id })
                    .then((t) => {
                        updateTransactionAndClearLocal(t);
                    })
                    .catch((err) => {
                        toast.error(err);
                    });
            }
        }
    };

    const confirmDeleteTransaction = () => {
        deleteTransaction({ transactionID: transaction.id })
            .then((t) => {
                // TODO: use recoil transaction
                updateTransactionInState(t, setTransactions);
                resetLocalPositionChanges();
                resetLocalTransactionChanges();
                history.push(`/groups/${groupID}/`);
            })
            .catch((err) => {
                toast.error(err);
            });
    };

    return (
        <>
            <Grid container justifyContent="space-between">
                <Grid item sx={{ display: "flex", alignItems: "center" }}>
                    <IconButton
                        sx={{ display: { xs: "none", md: "inline-flex" } }}
                        component={RouterLink}
                        to={`/groups/${groupID}/`}
                    >
                        <ChevronLeft />
                    </IconButton>
                    <Chip color="primary" label={transaction.type} />
                </Grid>
                <Grid item>
                    {userPermissions.can_write && (
                        <>
                            {transaction.is_wip ? (
                                <>
                                    <Button color="primary" onClick={commitEdit}>
                                        Save
                                    </Button>
                                    <Button color="error" onClick={abortEdit}>
                                        Cancel
                                    </Button>
                                </>
                            ) : (
                                <IconButton color="primary" onClick={edit}>
                                    <Edit />
                                </IconButton>
                            )}
                            <IconButton color="error" onClick={() => setConfirmDeleteDialogOpen(true)}>
                                <Delete />
                            </IconButton>
                        </>
                    )}
                </Grid>
            </Grid>
            <Dialog maxWidth="xs" aria-labelledby="confirmation-dialog-title" open={confirmDeleteDialogOpen}>
                <DialogTitle id="confirmation-dialog-title">Confirm delete transaction</DialogTitle>
                <DialogContent dividers>
                    Are you sure you want to delete the transaction "{transaction.description}"
                </DialogContent>
                <DialogActions>
                    <Button autoFocus onClick={() => setConfirmDeleteDialogOpen(false)} color="primary">
                        Cancel
                    </Button>
                    <Button onClick={confirmDeleteTransaction} color="error">
                        Ok
                    </Button>
                </DialogActions>
            </Dialog>
        </>
    );
}
Example #24
Source File: ItemViewer.tsx    From NekoMaid with MIT License 4 votes vote down vote up
ItemEditor: React.FC = () => {
  const plugin = usePlugin()
  const theme = useTheme()
  const [item, setItem] = useState<Item | undefined>()
  const [types, setTypes] = useState<string[]>([])
  const [tab, setTab] = useState(0)
  const [level, setLevel] = useState(1)
  const [enchantment, setEnchantment] = useState<string | undefined>()
  const [nbtText, setNBTText] = useState('')
  const nbt: NBT = item?.nbt ? parse(item.nbt) : { id: 'minecraft:' + (item?.type || 'air').toLowerCase(), Count: new Byte(1) } as any
  useEffect(() => {
    if (!item || types.length) return
    plugin.emit('item:fetch', (a: string[], b: string[]) => {
      setTypes(a)
      enchantments = b
    })
  }, [item])
  useEffect(() => {
    _setItem = (it: any) => {
      setItem(it)
      setNBTText(it.nbt ? stringify(parse(it.nbt), { pretty: true }) : '')
    }
    return () => { _setItem = null }
  }, [])
  const cancel = () => {
    setItem(undefined)
    if (_resolve) {
      _resolve(false)
      _resolve = null
    }
  }
  const update = () => {
    const newItem: any = { ...item }
    if (nbt) {
      newItem.nbt = stringify(nbt as any)
      setNBTText(stringify(nbt, { pretty: true }))
    }
    setItem(newItem)
  }
  const isAir = item?.type === 'AIR'
  const name = nbt?.tag?.display?.Name
  const enchantmentMap: Record<string, true> = { }
  return <Dialog open={!!item} onClose={cancel}>
    <DialogTitle>{lang.itemEditor.title}</DialogTitle>
    <DialogContent sx={{ display: 'flex', flexWrap: 'wrap', justifyContent: 'center' }}>
      {item && <Box sx={{ display: 'flex', width: '100%', justifyContent: 'center' }}>
        <ItemViewer item={item} />
        <Autocomplete
          options={types}
          sx={{ maxWidth: 300, marginLeft: 1, flexGrow: 1 }}
          value={item?.type}
          onChange={(_, it) => {
            item.type = it || 'AIR'
            if (nbt) nbt.id = 'minecraft:' + (it ? it.toLowerCase() : 'air')
            update()
          }}
          getOptionLabel={it => {
            const locatedName = getName(it.toLowerCase())
            return (locatedName ? locatedName + ' ' : '') + it
          }}
          renderInput={(params) => <TextField {...params} label={lang.itemEditor.itemType} size='small' variant='standard' />}
        />
      </Box>}
      <Tabs centered value={tab} onChange={(_, it) => setTab(it)} sx={{ marginBottom: 2 }}>
        <Tab label={lang.itemEditor.baseAttribute} disabled={isAir} />
        <Tab label={minecraft['container.enchant']} disabled={isAir} />
        <Tab label='NBT' disabled={isAir} />
      </Tabs>
      {nbt && tab === 0 && <Grid container spacing={1} rowSpacing={1}>
        <Grid item xs={12} md={6}><TextField
          fullWidth
          label={lang.itemEditor.count}
          type='number'
          variant='standard'
          value={nbt.Count}
          disabled={isAir}
          onChange={e => {
            nbt.Count = new Byte(item!.amount = parseInt(e.target.value))
            update()
          }}
        /></Grid>
        <Grid item xs={12} md={6}><TextField
          fullWidth
          label={lang.itemEditor.damage}
          type='number'
          variant='standard'
          value={nbt.tag?.Damage}
          disabled={isAir}
          onChange={e => {
            set(nbt, 'tag.Damage', parseInt(e.target.value))
            update()
          }}
        /></Grid>
        <Grid item xs={12} md={6}>
          <TextField
            fullWidth
            label={lang.itemEditor.displayName}
            variant='standard'
            disabled={isAir}
            value={name ? stringifyTextComponent(JSON.parse(name)) : ''}
            onChange={e => {
              set(nbt, 'tag.display.Name', JSON.stringify(item!.name = e.target.value))
              update()
            }}
          />
          <FormControlLabel
            label={minecraft['item.unbreakable']}
            disabled={isAir}
            checked={nbt.tag?.Unbreakable?.value === 1}
            control={<Checkbox
              checked={nbt.tag?.Unbreakable?.value === 1}
              onChange={e => {
                set(nbt, 'tag.Unbreakable', new Byte(+e.target.checked))
                update()
              }} />
            }
          />
        </Grid>
        <Grid item xs={12} md={6}><TextField
          fullWidth
          multiline
          label={lang.itemEditor.lore}
          variant='standard'
          maxRows={5}
          disabled={isAir}
          value={nbt.tag?.display?.Lore?.map(l => stringifyTextComponent(JSON.parse(l)))?.join('\n') || ''}
          onChange={e => {
            set(nbt, 'tag.display.Lore', e.target.value.split('\n').map(text => JSON.stringify(text)))
            update()
          }}
        /></Grid>
      </Grid>}
      {nbt && tab === 1 && <Grid container spacing={1} sx={{ width: '100%' }}>
        {nbt.tag?.Enchantments?.map((it, i) => {
          enchantmentMap[it.id] = true
          return <Grid item key={i}><Chip label={getEnchantmentName(it)} onDelete={() => {
            nbt?.tag?.Enchantments?.splice(i, 1)
            update()
          }} /></Grid>
        })}
        <Grid item><Chip label={lang.itemEditor.newEnchantment} color='primary' onClick={() => {
          setEnchantment('')
          setLevel(1)
        }} /></Grid>
        <Dialog onClose={() => setEnchantment(undefined)} open={enchantment != null}>
          <DialogTitle>{lang.itemEditor.newEnchantmentTitle}</DialogTitle>
          <DialogContent>
            <Box component='form' sx={{ display: 'flex', flexWrap: 'wrap' }}>
              <FormControl variant='standard' sx={{ m: 1, minWidth: 120 }}>
                <InputLabel htmlFor='item-editor-enchantment-selector'>{minecraft['container.enchant']}</InputLabel>
                <Select
                  id='item-editor-enchantment-selector'
                  label={minecraft['container.enchant']}
                  value={enchantment || ''}
                  onChange={e => setEnchantment(e.target.value)}
                >{enchantments
                  .filter(e => !(e in enchantmentMap))
                  .map(it => <MenuItem key={it} value={it}>{getEnchantmentName(it)}</MenuItem>)}
                </Select>
              </FormControl>
              <FormControl sx={{ m: 1, minWidth: 120 }}>
                <TextField
                  label={lang.itemEditor.level}
                  type='number'
                  variant='standard'
                  value={level}
                  onChange={e => setLevel(parseInt(e.target.value))}
                />
              </FormControl>
            </Box>
          </DialogContent>
          <DialogActions>
            <Button onClick={() => setEnchantment(undefined)}>{minecraft['gui.cancel']}</Button>
            <Button disabled={!enchantment || isNaN(level)} onClick={() => {
              if (nbt) {
                if (!nbt.tag) nbt.tag = { Damage: new Int(0) }
                ;(nbt.tag.Enchantments || (nbt.tag.Enchantments = [])).push({ id: enchantment!, lvl: new Short(level) })
              }
              setEnchantment(undefined)
              update()
            }}>{minecraft['gui.ok']}</Button>
          </DialogActions>
        </Dialog>
      </Grid>}
    </DialogContent>
    {nbt && tab === 2 && <Box sx={{
      '& .CodeMirror': { width: '100%' },
      '& .CodeMirror-dialog, .CodeMirror-scrollbar-filler': { backgroundColor: theme.palette.background.paper + '!important' }
    }}>
      <UnControlled
        value={nbtText}
        options={{
          mode: 'javascript',
          phrases: lang.codeMirrorPhrases,
          theme: theme.palette.mode === 'dark' ? 'material' : 'one-light'
        }}
        onChange={(_: any, __: any, nbt: string) => {
          const n = parse(nbt) as any as NBT
          const newItem: any = { ...item, nbt }
          if (n.Count?.value != null) newItem.amount = n.Count.value
          setItem(newItem)
        }}
      />
    </Box>}
    <DialogActions>
      <Button onClick={cancel}>{minecraft['gui.cancel']}</Button>
      <Button onClick={() => {
        setItem(undefined)
        if (_resolve) {
          _resolve(!item || item.type === 'AIR' ? null : item)
          _resolve = null
        }
      }}>{minecraft['gui.ok']}</Button>
    </DialogActions>
  </Dialog>
}
Example #25
Source File: ArtifactCard.tsx    From genshin-optimizer with MIT License 4 votes vote down vote up
export default function ArtifactCard({ artifactId, artifactObj, onClick, onDelete, mainStatAssumptionLevel = 0, effFilter = allSubstatFilter, probabilityFilter, disableEditSetSlot = false, editor = false, canExclude = false, canEquip = false, extraButtons }: Data): JSX.Element | null {
  const { t } = useTranslation(["artifact", "ui"]);
  const { database } = useContext(DatabaseContext)
  const databaseArtifact = useArtifact(artifactId)
  const sheet = usePromise(ArtifactSheet.get((artifactObj ?? databaseArtifact)?.setKey), [artifactObj, databaseArtifact])
  const equipOnChar = (charKey: CharacterKey | "") => database.setArtLocation(artifactId!, charKey)
  const editable = !artifactObj
  const [showEditor, setshowEditor] = useState(false)
  const onHideEditor = useCallback(() => setshowEditor(false), [setshowEditor])
  const onShowEditor = useCallback(() => editable && setshowEditor(true), [editable, setshowEditor])

  const wrapperFunc = useCallback(children => <CardActionArea onClick={() => artifactId && onClick?.(artifactId)} sx={{ flexGrow: 1, display: "flex", flexDirection: "column" }} >{children}</CardActionArea>, [onClick, artifactId],)
  const falseWrapperFunc = useCallback(children => <Box sx={{ flexGrow: 1, display: "flex", flexDirection: "column" }} >{children}</Box>, [])

  const art = artifactObj ?? databaseArtifact
  if (!art) return null

  const { id, lock, slotKey, rarity, level, mainStatKey, substats, exclude, location = "" } = art
  const mainStatLevel = Math.max(Math.min(mainStatAssumptionLevel, rarity * 4), level)
  const mainStatUnit = KeyMap.unit(mainStatKey)
  const levelVariant = "roll" + (Math.floor(Math.max(level, 0) / 4) + 1)
  const { currentEfficiency, maxEfficiency } = Artifact.getArtifactEfficiency(art, effFilter)
  const artifactValid = maxEfficiency !== 0
  const slotName = sheet?.getSlotName(slotKey) || "Unknown Piece Name"
  const slotDesc = sheet?.getSlotDesc(slotKey)
  const slotDescTooltip = slotDesc && <InfoTooltip title={<Box>
    <Typography variant='h6'>{slotName}</Typography>
    <Typography>{slotDesc}</Typography>
  </Box>} />
  const setEffects = sheet?.setEffects
  const setDescTooltip = sheet && setEffects && <InfoTooltip title={
    <span>
      {Object.keys(setEffects).map(setNumKey => <span key={setNumKey}>
        <Typography variant="h6"><SqBadge color="success">{t(`artifact:setEffectNum`, { setNum: setNumKey })}</SqBadge></Typography>
        <Typography>{sheet.setEffectDesc(setNumKey as any)}</Typography>
      </span>)}
    </span>
  } />
  return <Suspense fallback={<Skeleton variant="rectangular" sx={{ width: "100%", height: "100%", minHeight: 350 }} />}>
    {editor && <Suspense fallback={false}>
      <ArtifactEditor
        artifactIdToEdit={showEditor ? artifactId : ""}
        cancelEdit={onHideEditor}
        disableEditSetSlot={disableEditSetSlot}
      />
    </Suspense>}
    <CardLight sx={{ height: "100%", display: "flex", flexDirection: "column" }}>
      <ConditionalWrapper condition={!!onClick} wrapper={wrapperFunc} falseWrapper={falseWrapperFunc}>
        <Box className={`grad-${rarity}star`} sx={{ position: "relative", width: "100%" }}>
          {!onClick && <IconButton color="primary" disabled={!editable} onClick={() => database.updateArt({ lock: !lock }, id)} sx={{ position: "absolute", right: 0, bottom: 0, zIndex: 2 }}>
            {lock ? <Lock /> : <LockOpen />}
          </IconButton>}
          <Box sx={{ pt: 2, px: 2, position: "relative", zIndex: 1 }}>
            {/* header */}
            <Box component="div" sx={{ display: "flex", alignItems: "center", gap: 1, mb: 1 }}>
              <Chip size="small" label={<strong>{` +${level}`}</strong>} color={levelVariant as any} />
              <Typography component="span" noWrap sx={{ backgroundColor: "rgba(100,100,100,0.35)", borderRadius: "1em", px: 1 }}><strong>{slotName}</strong></Typography>
              <Box flexGrow={1} sx={{ textAlign: "right" }}>
                {slotDescTooltip}
              </Box>
            </Box>
            <Typography color="text.secondary" variant="body2">
              <SlotNameWithIcon slotKey={slotKey} />
            </Typography>
            <Typography variant="h6" color={`${KeyMap.getVariant(mainStatKey)}.main`}>
              <span>{StatIcon[mainStatKey]} {KeyMap.get(mainStatKey)}</span>
            </Typography>
            <Typography variant="h5">
              <strong>
                <ColorText color={mainStatLevel !== level ? "warning" : undefined}>{cacheValueString(Artifact.mainStatValue(mainStatKey, rarity, mainStatLevel) ?? 0, KeyMap.unit(mainStatKey))}{mainStatUnit}</ColorText>
              </strong>
            </Typography>
            <Stars stars={rarity} colored />
            {/* {process.env.NODE_ENV === "development" && <Typography color="common.black">{id || `""`} </Typography>} */}
          </Box>
          <Box sx={{ height: "100%", position: "absolute", right: 0, top: 0 }}>
            <Box
              component="img"
              src={sheet?.slotIcons[slotKey] ?? ""}
              width="auto"
              height="100%"
              sx={{ float: "right" }}
            />
          </Box>
        </Box>
        <CardContent sx={{ flexGrow: 1, display: "flex", flexDirection: "column", pt: 1, pb: 0, width: "100%" }}>
          {substats.map((stat: ICachedSubstat) => <SubstatDisplay key={stat.key} stat={stat} effFilter={effFilter} rarity={rarity} />)}
          <Box sx={{ display: "flex", my: 1 }}>
            <Typography color="text.secondary" component="span" variant="caption" sx={{ flexGrow: 1 }}>{t`artifact:editor.curSubEff`}</Typography>
            <PercentBadge value={currentEfficiency} max={900} valid={artifactValid} />
          </Box>
          {currentEfficiency !== maxEfficiency && <Box sx={{ display: "flex", mb: 1 }}>
            <Typography color="text.secondary" component="span" variant="caption" sx={{ flexGrow: 1 }}>{t`artifact:editor.maxSubEff`}</Typography>
            <PercentBadge value={maxEfficiency} max={900} valid={artifactValid} />
          </Box>}
          <Box flexGrow={1} />
          {probabilityFilter && <strong>Probability: {(probability(art, probabilityFilter) * 100).toFixed(2)}%</strong>}
          <Typography color="success.main">{sheet?.name ?? "Artifact Set"} {setDescTooltip}</Typography>
        </CardContent>
      </ConditionalWrapper>
      <Box sx={{ p: 1, display: "flex", gap: 1, justifyContent: "space-between", alignItems: "center" }}>
        {editable && canEquip
          ? <CharacterAutocomplete sx={{ flexGrow: 1 }} size="small" showDefault
            defaultIcon={<BusinessCenter />} defaultText={t("ui:inventory")}
            value={location} onChange={equipOnChar} />
          : <LocationName location={location} />}
        {editable && <ButtonGroup sx={{ height: "100%" }}>
          {editor && <Tooltip title={<Typography>{t`artifact:edit`}</Typography>} placement="top" arrow>
            <Button color="info" size="small" onClick={onShowEditor} >
              <FontAwesomeIcon icon={faEdit} className="fa-fw" />
            </Button>
          </Tooltip>}
          {canExclude && <Tooltip title={<Typography>{t`artifact:excludeArtifactTip`}</Typography>} placement="top" arrow>
            <Button onClick={() => database.updateArt({ exclude: !exclude }, id)} color={exclude ? "error" : "success"} size="small" >
              <FontAwesomeIcon icon={exclude ? faBan : faChartLine} className="fa-fw" />
            </Button>
          </Tooltip>}
          {!!onDelete && <Button color="error" size="small" onClick={() => onDelete(id)} disabled={lock}>
            <FontAwesomeIcon icon={faTrashAlt} className="fa-fw" />
          </Button>}
          {extraButtons}
        </ButtonGroup>}
      </Box>
    </CardLight >
  </Suspense>
}
Example #26
Source File: AccountList.tsx    From abrechnung with GNU Affero General Public License v3.0 4 votes vote down vote up
export default function AccountList({ group }) {
    const [speedDialOpen, setSpeedDialOpen] = useState(false);
    const toggleSpeedDial = () => setSpeedDialOpen((currValue) => !currValue);

    const [showPersonalAccountCreationModal, setShowPersonalAccountCreationModal] = useState(false);
    const [showClearingAccountCreationModal, setShowClearingAccountCreationModal] = useState(false);

    const [activeTab, setActiveTab] = useState("personal");
    const [searchValuePersonal, setSearchValuePersonal] = useState("");
    const [searchValueClearing, setSearchValueClearing] = useState("");

    const [showPersonalAccountEditModal, setShowPersonalAccountEditModal] = useState(false);
    const [showClearingAccountEditModal, setShowClearingAccountEditModal] = useState(false);
    const [clearingAccountToCopy, setClearingAccountToCopy] = useState(undefined);
    const [accountToEdit, setAccountToEdit] = useState(null);
    const [clearingAccountToEdit, setClearingAccountToEdit] = useState(null);
    const setAccounts = useSetRecoilState(groupAccounts(group.id));
    const personalAccounts = useRecoilValue(personalAccountsSeenByUser(group.id));
    const clearingAccounts = useRecoilValue(clearingAccountsSeenByUser(group.id));
    const allAccounts = useRecoilValue(accountsSeenByUser(group.id));
    const [accountToDelete, setAccountToDelete] = useState(null);
    const userPermissions = useRecoilValue(currUserPermissions(group.id));
    const currentUser = useRecoilValue(userData);
    const memberIDToUsername = useRecoilValue(groupMemberIDsToUsername(group.id));

    const [filteredPersonalAccounts, setFilteredPersonalAccounts] = useState([]);
    const [filteredClearingAccounts, setFilteredClearingAccounts] = useState([]);
    useEffect(() => {
        if (searchValuePersonal != null && searchValuePersonal !== "") {
            setFilteredPersonalAccounts(
                personalAccounts.filter((t) => {
                    return (
                        t.name.toLowerCase().includes(searchValuePersonal.toLowerCase()) ||
                        t.description.toLowerCase().includes(searchValuePersonal.toLowerCase())
                    );
                })
            );
        } else {
            return setFilteredPersonalAccounts(personalAccounts);
        }
    }, [personalAccounts, searchValuePersonal, setFilteredPersonalAccounts]);

    useEffect(() => {
        if (searchValueClearing != null && searchValueClearing !== "") {
            setFilteredClearingAccounts(
                clearingAccounts.filter((t) => {
                    return (
                        t.name.toLowerCase().includes(searchValueClearing.toLowerCase()) ||
                        t.description.toLowerCase().includes(searchValueClearing.toLowerCase())
                    );
                })
            );
        } else {
            return setFilteredClearingAccounts(clearingAccounts);
        }
    }, [clearingAccounts, searchValueClearing, setFilteredClearingAccounts]);

    useTitle(`${group.name} - Accounts`);

    const openAccountEdit = (account) => {
        setAccountToEdit(account);
        setShowPersonalAccountEditModal(true);
    };

    const closeAccountEdit = (evt, reason) => {
        if (reason !== "backdropClick") {
            setShowPersonalAccountEditModal(false);
            setAccountToEdit(null);
        }
    };

    const openClearingAccountEdit = (account) => {
        setClearingAccountToEdit(account);
        setShowClearingAccountEditModal(true);
    };

    const closeClearingAccountEdit = (evt, reason) => {
        if (reason !== "backdropClick") {
            setShowClearingAccountEditModal(false);
            setClearingAccountToEdit(null);
        }
    };

    const confirmDeleteAccount = () => {
        if (accountToDelete !== null) {
            deleteAccount({ accountID: accountToDelete })
                .then((account) => {
                    updateAccount(account, setAccounts);
                    setAccountToDelete(null);
                })
                .catch((err) => {
                    toast.error(err);
                });
        }
    };

    const openCreateDialog = () => {
        setClearingAccountToCopy(undefined);
        setShowClearingAccountCreationModal(true);
    };

    const copyClearingAccount = (account) => {
        setClearingAccountToCopy(account);
        setShowClearingAccountCreationModal(true);
    };

    return (
        <>
            <MobilePaper>
                <TabContext value={activeTab}>
                    <Box sx={{ borderBottom: 1, borderColor: "divider" }}>
                        <TabList onChange={(e, newValue) => setActiveTab(newValue)} centered>
                            <Tab
                                value="personal"
                                label={
                                    <TextBadge badgeContent={personalAccounts.length} color="primary">
                                        <span>Personal Accounts</span>
                                    </TextBadge>
                                }
                            />
                            <Tab
                                label={
                                    <TextBadge badgeContent={clearingAccounts.length} color="primary">
                                        <span>Clearing Accounts</span>
                                    </TextBadge>
                                }
                                value="clearing"
                            />
                        </TabList>
                    </Box>
                    <TabPanel value="personal">
                        <List>
                            {personalAccounts.length === 0 ? (
                                <Alert severity="info">No Accounts</Alert>
                            ) : (
                                <>
                                    <ListItem>
                                        <Input
                                            value={searchValuePersonal}
                                            onChange={(e) => setSearchValuePersonal(e.target.value)}
                                            placeholder="Search…"
                                            inputProps={{
                                                "aria-label": "search",
                                            }}
                                            endAdornment={
                                                <InputAdornment position="end">
                                                    <IconButton
                                                        aria-label="clear search input"
                                                        onClick={(e) => setSearchValuePersonal("")}
                                                        edge="end"
                                                    >
                                                        <Clear />
                                                    </IconButton>
                                                </InputAdornment>
                                            }
                                        />
                                    </ListItem>
                                    <Divider />
                                    {filteredPersonalAccounts.map((account) => (
                                        <ListItem sx={{ padding: 0 }} key={account.id}>
                                            <ListItemLink to={`/groups/${group.id}/accounts/${account.id}`}>
                                                <ListItemText
                                                    primary={
                                                        <div>
                                                            <span>{account.name}</span>
                                                            {account.owning_user_id === currentUser.id ? (
                                                                <span>
                                                                    , owned by{" "}
                                                                    <Chip
                                                                        size="small"
                                                                        component="span"
                                                                        color="primary"
                                                                        label="you"
                                                                    />
                                                                </span>
                                                            ) : (
                                                                account.owning_user_id !== null && (
                                                                    <span>
                                                                        , owned by{" "}
                                                                        <Chip
                                                                            size="small"
                                                                            component="span"
                                                                            color="secondary"
                                                                            label={
                                                                                memberIDToUsername[
                                                                                    account.owning_user_id
                                                                                ]
                                                                            }
                                                                        />
                                                                    </span>
                                                                )
                                                            )}
                                                        </div>
                                                    }
                                                    secondary={account.description}
                                                />
                                            </ListItemLink>
                                            {userPermissions.can_write && (
                                                <ListItemSecondaryAction>
                                                    <IconButton
                                                        color="primary"
                                                        onClick={() => openAccountEdit(account)}
                                                    >
                                                        <Edit />
                                                    </IconButton>
                                                    <IconButton
                                                        color="error"
                                                        onClick={() => setAccountToDelete(account.id)}
                                                    >
                                                        <Delete />
                                                    </IconButton>
                                                </ListItemSecondaryAction>
                                            )}
                                        </ListItem>
                                    ))}
                                </>
                            )}
                        </List>
                        {userPermissions.can_write && (
                            <>
                                <Grid container justifyContent="center">
                                    <Tooltip title="Create Personal Account">
                                        <IconButton
                                            color="primary"
                                            onClick={() => setShowPersonalAccountCreationModal(true)}
                                        >
                                            <Add />
                                        </IconButton>
                                    </Tooltip>
                                </Grid>
                                <CreateAccountModal
                                    show={showPersonalAccountCreationModal}
                                    onClose={(evt, reason) => {
                                        if (reason !== "backdropClick") {
                                            setShowPersonalAccountCreationModal(false);
                                        }
                                    }}
                                    group={group}
                                />
                                <EditAccountModal
                                    show={showPersonalAccountEditModal}
                                    onClose={closeAccountEdit}
                                    account={accountToEdit}
                                    group={group}
                                />
                            </>
                        )}
                    </TabPanel>
                    <TabPanel value="clearing">
                        <List>
                            {clearingAccounts.length === 0 ? (
                                <Alert severity="info">No Accounts</Alert>
                            ) : (
                                <>
                                    <ListItem>
                                        <Input
                                            value={searchValueClearing}
                                            onChange={(e) => setSearchValueClearing(e.target.value)}
                                            placeholder="Search…"
                                            inputProps={{
                                                "aria-label": "search",
                                            }}
                                            endAdornment={
                                                <InputAdornment position="end">
                                                    <IconButton
                                                        aria-label="clear search input"
                                                        onClick={(e) => setSearchValueClearing("")}
                                                        edge="end"
                                                    >
                                                        <Clear />
                                                    </IconButton>
                                                </InputAdornment>
                                            }
                                        />
                                    </ListItem>
                                    <Divider />
                                    {filteredClearingAccounts.map((account) => (
                                        <ListItem sx={{ padding: 0 }} key={account.id}>
                                            <ListItemLink to={`/groups/${group.id}/accounts/${account.id}`}>
                                                <ListItemText primary={account.name} secondary={account.description} />
                                            </ListItemLink>
                                            {userPermissions.can_write && (
                                                <ListItemSecondaryAction>
                                                    <IconButton
                                                        color="primary"
                                                        onClick={() => openClearingAccountEdit(account)}
                                                    >
                                                        <Edit />
                                                    </IconButton>
                                                    <IconButton
                                                        color="primary"
                                                        onClick={() => copyClearingAccount(account)}
                                                    >
                                                        <ContentCopy />
                                                    </IconButton>
                                                    <IconButton
                                                        color="error"
                                                        onClick={() => setAccountToDelete(account.id)}
                                                    >
                                                        <Delete />
                                                    </IconButton>
                                                </ListItemSecondaryAction>
                                            )}
                                        </ListItem>
                                    ))}
                                </>
                            )}
                        </List>
                        {userPermissions.can_write && (
                            <>
                                <Grid container justifyContent="center">
                                    <Tooltip title="Create Clearing Account">
                                        <IconButton color="primary" onClick={openCreateDialog}>
                                            <Add />
                                        </IconButton>
                                    </Tooltip>
                                </Grid>
                                <CreateClearingAccountModal
                                    show={showClearingAccountCreationModal}
                                    onClose={(evt, reason) => {
                                        if (reason !== "backdropClick") {
                                            setShowClearingAccountCreationModal(false);
                                        }
                                    }}
                                    initialValues={clearingAccountToCopy}
                                    group={group}
                                />
                                <EditClearingAccountModal
                                    show={showClearingAccountEditModal}
                                    onClose={closeClearingAccountEdit}
                                    account={clearingAccountToEdit}
                                    group={group}
                                />
                            </>
                        )}
                    </TabPanel>
                </TabContext>
            </MobilePaper>
            {userPermissions.can_write && (
                <>
                    <SpeedDial
                        ariaLabel="Create Account"
                        sx={{ position: "fixed", bottom: 20, right: 20 }}
                        icon={<SpeedDialIcon />}
                        // onClose={() => setSpeedDialOpen(false)}
                        // onOpen={() => setSpeedDialOpen(true)}
                        onClick={toggleSpeedDial}
                        open={speedDialOpen}
                    >
                        <SpeedDialAction
                            icon={<PersonalAccountIcon />}
                            tooltipTitle="Personal"
                            tooltipOpen
                            onClick={() => setShowPersonalAccountCreationModal(true)}
                        />
                        <SpeedDialAction
                            icon={<ClearingAccountIcon />}
                            tooltipTitle="Clearing"
                            tooltipOpen
                            onClick={openCreateDialog}
                        />
                    </SpeedDial>

                    <Dialog maxWidth="xs" aria-labelledby="confirmation-dialog-title" open={accountToDelete !== null}>
                        <DialogTitle id="confirmation-dialog-title">Confirm delete account</DialogTitle>
                        <DialogContent dividers>
                            Are you sure you want to delete the account "
                            {allAccounts.find((acc) => acc.id === accountToDelete)?.name}"
                        </DialogContent>
                        <DialogActions>
                            <Button autoFocus onClick={() => setAccountToDelete(null)} color="primary">
                                Cancel
                            </Button>
                            <Button onClick={confirmDeleteAccount} color="error">
                                Ok
                            </Button>
                        </DialogActions>
                    </Dialog>
                </>
            )}
        </>
    );
}
Example #27
Source File: GroupMemberList.tsx    From abrechnung with GNU Affero General Public License v3.0 4 votes vote down vote up
export default function GroupMemberList({ group }) {
    const [showEditMemberDialog, setShowEditMemberDialog] = useState(false);
    const [memberToEdit, setMemberToEdit] = useState(null);
    const currentUser = useRecoilValue(userData);
    const members = useRecoilValue(groupMembers(group.id));
    const permissions = useRecoilValue(currUserPermissions(group.id));

    useTitle(`${group.name} - Members`);

    const handleEditMemberSubmit = (values, { setSubmitting }) => {
        updateGroupMemberPrivileges({
            groupID: group.id,
            userID: values.userID,
            canWrite: values.canWrite,
            isOwner: values.isOwner,
        })
            .then((result) => {
                setSubmitting(false);
                setShowEditMemberDialog(false);
                toast.success("Successfully updated group member permissions");
            })
            .catch((err) => {
                setSubmitting(false);
                toast.error(err);
            });
    };

    const getMemberUsername = (member_id) => {
        const member = members.find((member) => member.user_id === member_id);
        if (member === undefined) {
            return "unknown";
        }
        return member.username;
    };

    const closeEditMemberModal = () => {
        setShowEditMemberDialog(false);
        setMemberToEdit(null);
    };

    const openEditMemberModal = (userID) => {
        const user = members.find((member) => member.user_id === userID);
        // TODO: maybe deal with disappearing users in the list
        setMemberToEdit(user);
        setShowEditMemberDialog(true);
    };

    return (
        <MobilePaper>
            <List>
                {members.length === 0 ? (
                    <ListItem>
                        <ListItemText primary="No Members" />
                    </ListItem>
                ) : (
                    members.map((member, index) => (
                        <ListItem key={index}>
                            <ListItemText
                                primary={
                                    <>
                                        <span style={{ marginRight: 5 }}>{member.username}</span>
                                        {member.is_owner ? (
                                            <Chip
                                                size="small"
                                                sx={{ mr: 1 }}
                                                component="span"
                                                color="primary"
                                                label="owner"
                                                variant="outlined"
                                            />
                                        ) : member.can_write ? (
                                            <Chip
                                                size="small"
                                                sx={{ mr: 1 }}
                                                component="span"
                                                color="primary"
                                                label="editor"
                                                variant="outlined"
                                            />
                                        ) : null}
                                        {member.user_id === currentUser.id ? (
                                            <Chip
                                                size="small"
                                                sx={{ mr: 1 }}
                                                component="span"
                                                color="primary"
                                                label="it's you"
                                            />
                                        ) : (
                                            ""
                                        )}
                                    </>
                                }
                                secondary={
                                    <>
                                        {member.invited_by && (
                                            <small className="text-muted">
                                                invited by {getMemberUsername(member.invited_by)}
                                            </small>
                                        )}
                                        <small className="text-muted">
                                            joined{" "}
                                            {DateTime.fromISO(member.joined_at).toLocaleString(DateTime.DATETIME_FULL)}
                                        </small>
                                    </>
                                }
                            />
                            {permissions.is_owner || permissions.can_write ? (
                                <ListItemSecondaryAction>
                                    <IconButton onClick={() => openEditMemberModal(member.user_id)}>
                                        <Edit />
                                    </IconButton>
                                </ListItemSecondaryAction>
                            ) : (
                                ""
                            )}
                        </ListItem>
                    ))
                )}
            </List>
            <Dialog open={showEditMemberDialog} onClose={closeEditMemberModal}>
                <DialogTitle>Edit Group Member</DialogTitle>
                <DialogContent>
                    <Formik
                        initialValues={{
                            userID: memberToEdit ? memberToEdit.user_id : -1,
                            isOwner: memberToEdit ? memberToEdit.is_owner : false,
                            canWrite: memberToEdit ? memberToEdit.can_write : false,
                        }}
                        onSubmit={handleEditMemberSubmit}
                        enableReinitialize={true}
                    >
                        {({ values, handleBlur, handleChange, handleSubmit, isSubmitting }) => (
                            <Form>
                                <FormControlLabel
                                    control={
                                        <Checkbox
                                            name="canWrite"
                                            onBlur={handleBlur}
                                            onChange={handleChange}
                                            checked={values.canWrite}
                                        />
                                    }
                                    label="Can Write"
                                />
                                <FormControlLabel
                                    control={
                                        <Checkbox
                                            name="isOwner"
                                            onBlur={handleBlur}
                                            onChange={handleChange}
                                            checked={values.isOwner}
                                        />
                                    }
                                    label="Is Owner"
                                />

                                {isSubmitting && <LinearProgress />}
                                <DialogActions>
                                    <Button type="submit" color="primary">
                                        Save
                                    </Button>
                                    <Button color="error" onClick={closeEditMemberModal}>
                                        Close
                                    </Button>
                                </DialogActions>
                            </Form>
                        )}
                    </Formik>
                </DialogContent>
            </Dialog>
        </MobilePaper>
    );
}
Example #28
Source File: FireCMSAppBar.tsx    From firecms with MIT License 4 votes vote down vote up
export function FireCMSAppBar({
                                  title,
                                  handleDrawerToggle,
                                  toolbarExtraWidget
                              }: CMSAppBarProps) {

    const classes = useStyles();

    const breadcrumbsContext = useBreadcrumbsContext();
    const { breadcrumbs } = breadcrumbsContext;

    const authController = useAuthController();
    const { mode, toggleMode } = useModeState();

    const initial = authController.user?.displayName
        ? authController.user.displayName[0].toUpperCase()
        : (authController.user?.email ? authController.user.email[0].toUpperCase() : "A");

    return (
        <Slide
            direction="down" in={true} mountOnEnter unmountOnExit>
            <AppBar
                className={classes.appbar}
                position={"relative"}
                elevation={1}>
                <Toolbar>
                    <IconButton
                        color="inherit"
                        aria-label="Open drawer"
                        edge="start"
                        onClick={handleDrawerToggle}
                        className={classes.menuButton}
                        size="large">
                        <MenuIcon/>
                    </IconButton>

                    <Hidden lgDown>
                        <Box mr={3}>
                            <Link
                                underline={"none"}
                                key={"breadcrumb-home"}
                                color="inherit"
                                component={ReactLink}
                                to={"/"}>
                                <Typography variant="h6" noWrap>
                                    {title}
                                </Typography>
                            </Link>
                        </Box>
                    </Hidden>

                    <Box mr={2}>
                        <Breadcrumbs
                            separator={<NavigateNextIcon
                                htmlColor={"rgb(0,0,0,0.87)"}
                                fontSize="small"/>}
                            aria-label="breadcrumb">
                            {breadcrumbs.map((entry, index) => (
                                <Link
                                    underline={"none"}
                                    key={`breadcrumb-${index}`}
                                    color="inherit"
                                    component={ReactLink}
                                    to={entry.url}>
                                    <Chip
                                        classes={{ root: classes.breadcrumb }}
                                        label={entry.title}
                                    />
                                </Link>)
                            )
                            }
                        </Breadcrumbs>
                    </Box>

                    <Box flexGrow={1}/>

                    {toolbarExtraWidget &&
                    <ErrorBoundary>
                        {
                            toolbarExtraWidget
                        }
                    </ErrorBoundary>}


                    <Box p={1} mr={1}>
                        <IconButton
                            color="inherit"
                            aria-label="Open drawer"
                            edge="start"
                            onClick={() => toggleMode()}
                            size="large">
                            {mode === "dark"
                                ? <Brightness3Icon/>
                                : <Brightness5Icon/>}
                        </IconButton>
                    </Box>

                    <Box p={1} mr={1}>
                        {authController.user && authController.user.photoURL
                            ? <Avatar
                                src={authController.user.photoURL}/>
                            : <Avatar>{initial}</Avatar>
                        }
                    </Box>

                    <Button variant="text"
                            color="inherit"
                            onClick={authController.signOut}>
                        Log Out
                    </Button>

                </Toolbar>
            </AppBar>
        </Slide>
    );
}
Example #29
Source File: MultiSelectElement.tsx    From react-hook-form-mui with MIT License 4 votes vote down vote up
export default function MultiSelectElement({
  menuItems,
  label = '',
  itemKey = '',
  itemValue = '',
  itemLabel = '',
  required = false,
  validation = {},
  parseError,
  name,
  menuMaxHeight = ITEM_HEIGHT * 4.5 + ITEM_PADDING_TOP,
  menuMaxWidth = 250,
  minWidth = 120,
  helperText,
  showChips,
  variant,
  control,
  showCheckbox,
  ...rest
}: MultiSelectElementProps): JSX.Element {

  if (required) {
    validation.required = 'This field is required'
  }

  return (
    <Controller
      name={name}
      rules={validation}
      control={control}
      render={({ field: { value, onChange, onBlur }, fieldState: { invalid, error } }) => {
        helperText = error ? (typeof parseError === 'function' ? parseError(error) : error.message) : helperText
        return (
          <FormControl
            variant={variant}
            style={{ minWidth }}
            fullWidth={rest.fullWidth}
            error={invalid}
          >
            {label && (
              <InputLabel error={invalid} htmlFor={rest.id || `select-multi-select-${name}`} required={required}>
                {label}
              </InputLabel>
            )}
            <Select
              {...rest}
              id={rest.id || `select-multi-select-${name}`}
              multiple
              label={label || undefined}
              error={invalid}
              value={value || []}
              required={required}
              onChange={onChange}
              onBlur={onBlur}
              MenuProps={{
                PaperProps: {
                  style: {
                    maxHeight: menuMaxHeight,
                    width: menuMaxWidth
                  }
                }
              }}
              renderValue={typeof rest.renderValue === 'function' ? rest.renderValue : showChips ? (selected) => (
                <div style={{ display: 'flex', flexWrap: 'wrap' }}>
                  {(selected as any[] || []).map((selectedValue) => (
                    <Chip
                      key={selectedValue}
                      label={selectedValue}
                      style={{ display: 'flex', flexWrap: 'wrap' }}
                      onDelete={() => {
                        onChange(value.filter((i: any) => i !== selectedValue))
                        // setValue(name, formValue.filter((i: any) => i !== value), { shouldValidate: true })
                      }}
                      deleteIcon={<CloseIcon
                        onMouseDown={(ev) => {
                          ev.stopPropagation()
                        }} />
                      }
                    />
                  ))}
                </div>
              ) : (selected) => selected?.join(', ')}
            >
              {menuItems.map((item: any) => {
                const isChecked = value?.includes(item) ?? false
                const key = itemValue || itemKey
                let val = key ? item[key] : item
                return (
                  <MenuItem
                    key={val}
                    value={val}
                    sx={{
                      fontWeight: (theme) => isChecked ? theme.typography.fontWeightBold : theme.typography.fontWeightRegular
                    }}
                  >
                    {showCheckbox && <Checkbox checked={isChecked} />}
                    <ListItemText primary={itemLabel ? item[itemLabel] : item} />
                  </MenuItem>
                )
              })}
            </Select>
            {helperText && <FormHelperText>{helperText}</FormHelperText>}
          </FormControl>
        )
      }}
    />
  )
}