170 lines
4.3 KiB
TypeScript
170 lines
4.3 KiB
TypeScript
import React, { useMemo, useState } from "react";
|
|
|
|
import {
|
|
Button,
|
|
Divider,
|
|
IconButton,
|
|
ListItemText,
|
|
Menu,
|
|
MenuItem,
|
|
TextField,
|
|
Tooltip,
|
|
} from "@mui/material";
|
|
|
|
import BookmarkBorderIcon from "@mui/icons-material/BookmarkBorder";
|
|
import DeleteOutlineIcon from "@mui/icons-material/DeleteOutline";
|
|
|
|
import { useI18n } from "../i18n/I18nProvider";
|
|
|
|
export type SavedViewParams = {
|
|
q?: string;
|
|
status?: string;
|
|
companyId?: number;
|
|
location?: string;
|
|
needsFollowUp?: boolean;
|
|
};
|
|
|
|
type SavedView = {
|
|
id: string;
|
|
name: string;
|
|
params: SavedViewParams;
|
|
createdAt: string;
|
|
};
|
|
|
|
const KEY = "jt_saved_views_v1";
|
|
|
|
function loadViews(): SavedView[] {
|
|
try {
|
|
const raw = window.localStorage.getItem(KEY);
|
|
if (!raw) return [];
|
|
const v = JSON.parse(raw);
|
|
if (!Array.isArray(v)) return [];
|
|
return v as SavedView[];
|
|
} catch {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
function saveViews(views: SavedView[]) {
|
|
window.localStorage.setItem(KEY, JSON.stringify(views));
|
|
}
|
|
|
|
export default function SavedViewsMenu({
|
|
current,
|
|
onApply,
|
|
}: {
|
|
current: SavedViewParams;
|
|
onApply: (p: SavedViewParams) => void;
|
|
}) {
|
|
const { t } = useI18n();
|
|
const [anchor, setAnchor] = useState<HTMLElement | null>(null);
|
|
const [name, setName] = useState("");
|
|
const [views, setViews] = useState<SavedView[]>(() => loadViews());
|
|
|
|
const hasAny = views.length > 0;
|
|
|
|
const canSave = useMemo(() => name.trim().length > 0, [name]);
|
|
|
|
const open = Boolean(anchor);
|
|
|
|
const add = () => {
|
|
if (!canSave) return;
|
|
const next: SavedView = {
|
|
id: `v_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`,
|
|
name: name.trim(),
|
|
params: { ...current },
|
|
createdAt: new Date().toISOString(),
|
|
};
|
|
const updated = [next, ...views].slice(0, 50);
|
|
setViews(updated);
|
|
saveViews(updated);
|
|
setName("");
|
|
};
|
|
|
|
const remove = (id: string) => {
|
|
const updated = views.filter((v) => v.id !== id);
|
|
setViews(updated);
|
|
saveViews(updated);
|
|
};
|
|
|
|
return (
|
|
<>
|
|
<Tooltip title={t("savedViewsTooltip")}>
|
|
<IconButton size="small" onClick={(e) => setAnchor(e.currentTarget)}>
|
|
<BookmarkBorderIcon fontSize="small" />
|
|
</IconButton>
|
|
</Tooltip>
|
|
|
|
<Menu
|
|
anchorEl={anchor}
|
|
open={open}
|
|
onClose={() => setAnchor(null)}
|
|
PaperProps={{ sx: { minWidth: 320, p: 0.5 } }}
|
|
>
|
|
<MenuItem disableRipple sx={{ cursor: "default", alignItems: "flex-start" }}>
|
|
<ListItemText
|
|
primary={t("savedViewsTitle")}
|
|
secondary={t("savedViewsSubtitle")}
|
|
/>
|
|
</MenuItem>
|
|
|
|
<MenuItem disableRipple sx={{ cursor: "default" }}>
|
|
<TextField
|
|
size="small"
|
|
label={t("savedViewsName")}
|
|
value={name}
|
|
onChange={(e) => setName(e.target.value)}
|
|
fullWidth
|
|
/>
|
|
</MenuItem>
|
|
<MenuItem disableRipple sx={{ cursor: "default", justifyContent: "flex-end" }}>
|
|
<Button variant="contained" size="small" onClick={add} disabled={!canSave}>
|
|
{t("savedViewsSaveCurrent")}
|
|
</Button>
|
|
</MenuItem>
|
|
|
|
<Divider />
|
|
|
|
{!hasAny && (
|
|
<MenuItem disabled>{t("savedViewsEmpty")}</MenuItem>
|
|
)}
|
|
|
|
{views.map((v) => (
|
|
<MenuItem
|
|
key={v.id}
|
|
onClick={() => {
|
|
onApply(v.params);
|
|
setAnchor(null);
|
|
}}
|
|
sx={{ gap: 1 }}
|
|
>
|
|
<ListItemText
|
|
primary={v.name}
|
|
secondary={
|
|
[
|
|
v.params.status ? t("savedViewsStatusLabel", { value: v.params.status }) : null,
|
|
v.params.location ? t("savedViewsLocationLabel", { value: v.params.location }) : null,
|
|
v.params.needsFollowUp ? t("savedViewsNeedsFollowUp") : null,
|
|
]
|
|
.filter(Boolean)
|
|
.join(" · ") || t("savedViewsNoFilters")
|
|
}
|
|
/>
|
|
<IconButton
|
|
size="small"
|
|
onClick={(e) => {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
remove(v.id);
|
|
}}
|
|
>
|
|
<DeleteOutlineIcon fontSize="small" />
|
|
</IconButton>
|
|
</MenuItem>
|
|
))}
|
|
</Menu>
|
|
</>
|
|
);
|
|
}
|
|
|