@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 |
/**
* @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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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>
)
}}
/>
)
}