mirror of
https://github.com/eliasstepanik/core.git
synced 2026-01-11 09:58:28 +00:00
fix: add option to remove episode from space
This commit is contained in:
parent
a14b83d66d
commit
3bdf051b32
@ -1,5 +1,4 @@
|
|||||||
import ReactMarkdown from "react-markdown";
|
import ReactMarkdown, {type Components } from "react-markdown";
|
||||||
import type { Components } from "react-markdown";
|
|
||||||
import { cn } from "~/lib/utils";
|
import { cn } from "~/lib/utils";
|
||||||
|
|
||||||
const markdownComponents: Components = {
|
const markdownComponents: Components = {
|
||||||
|
|||||||
112
apps/webapp/app/components/spaces/space-episode-actions.tsx
Normal file
112
apps/webapp/app/components/spaces/space-episode-actions.tsx
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
import { EllipsisVertical, Trash } from "lucide-react";
|
||||||
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
} from "../ui/dropdown-menu";
|
||||||
|
import { Button } from "../ui/button";
|
||||||
|
import {
|
||||||
|
AlertDialog,
|
||||||
|
AlertDialogAction,
|
||||||
|
AlertDialogCancel,
|
||||||
|
AlertDialogContent,
|
||||||
|
AlertDialogDescription,
|
||||||
|
AlertDialogFooter,
|
||||||
|
AlertDialogHeader,
|
||||||
|
AlertDialogTitle,
|
||||||
|
} from "../ui/alert-dialog";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { useFetcher, useNavigate } from "@remix-run/react";
|
||||||
|
import { toast } from "~/hooks/use-toast";
|
||||||
|
|
||||||
|
interface SpaceEpisodeActionsProps {
|
||||||
|
episodeId: string;
|
||||||
|
spaceId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SpaceEpisodeActions = ({
|
||||||
|
episodeId,
|
||||||
|
spaceId,
|
||||||
|
}: SpaceEpisodeActionsProps) => {
|
||||||
|
const [removeDialogOpen, setRemoveDialogOpen] = useState(false);
|
||||||
|
const removeFetcher = useFetcher();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const handleRemove = () => {
|
||||||
|
removeFetcher.submit(
|
||||||
|
{
|
||||||
|
episodeIds: JSON.stringify([episodeId]),
|
||||||
|
spaceId,
|
||||||
|
action: "remove",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
method: "post",
|
||||||
|
action: "/api/v1/episodes/assign-space",
|
||||||
|
encType: "application/json",
|
||||||
|
},
|
||||||
|
);
|
||||||
|
setRemoveDialogOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (removeFetcher.state === "idle" && removeFetcher.data) {
|
||||||
|
if (removeFetcher.data.success) {
|
||||||
|
toast({
|
||||||
|
title: "Success",
|
||||||
|
description: "Episode removed from space",
|
||||||
|
});
|
||||||
|
// Reload the page to refresh the episode list
|
||||||
|
navigate(".", { replace: true });
|
||||||
|
} else {
|
||||||
|
toast({
|
||||||
|
title: "Error",
|
||||||
|
description: removeFetcher.data.error || "Failed to remove episode",
|
||||||
|
variant: "destructive",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [removeFetcher.state, removeFetcher.data, navigate]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
className="h-6 w-6 shrink-0 items-center justify-center p-0 opacity-0 transition-opacity group-hover:opacity-100"
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
>
|
||||||
|
<EllipsisVertical size={16} />
|
||||||
|
</Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
|
||||||
|
<DropdownMenuContent align="end" onClick={(e) => e.stopPropagation()}>
|
||||||
|
<DropdownMenuItem onClick={() => setRemoveDialogOpen(true)}>
|
||||||
|
<Button variant="link" size="sm" className="gap-2 rounded">
|
||||||
|
<Trash size={15} /> Remove from space
|
||||||
|
</Button>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
|
||||||
|
<AlertDialog open={removeDialogOpen} onOpenChange={setRemoveDialogOpen}>
|
||||||
|
<AlertDialogContent onClick={(e) => e.stopPropagation()}>
|
||||||
|
<AlertDialogHeader>
|
||||||
|
<AlertDialogTitle>Remove from space</AlertDialogTitle>
|
||||||
|
<AlertDialogDescription>
|
||||||
|
Are you sure you want to remove this episode from the space? This
|
||||||
|
will not delete the episode itself.
|
||||||
|
</AlertDialogDescription>
|
||||||
|
</AlertDialogHeader>
|
||||||
|
<AlertDialogFooter>
|
||||||
|
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||||
|
<AlertDialogAction onClick={handleRemove}>
|
||||||
|
Remove
|
||||||
|
</AlertDialogAction>
|
||||||
|
</AlertDialogFooter>
|
||||||
|
</AlertDialogContent>
|
||||||
|
</AlertDialog>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -5,6 +5,7 @@ import { cn } from "~/lib/utils";
|
|||||||
import { useNavigate } from "@remix-run/react";
|
import { useNavigate } from "@remix-run/react";
|
||||||
import Markdown from "react-markdown";
|
import Markdown from "react-markdown";
|
||||||
import { StyledMarkdown } from "../common/styled-markdown";
|
import { StyledMarkdown } from "../common/styled-markdown";
|
||||||
|
import { SpaceEpisodeActions } from "./space-episode-actions";
|
||||||
|
|
||||||
export interface Episode {
|
export interface Episode {
|
||||||
uuid: string;
|
uuid: string;
|
||||||
@ -20,9 +21,10 @@ export interface Episode {
|
|||||||
|
|
||||||
interface SpaceFactCardProps {
|
interface SpaceFactCardProps {
|
||||||
episode: Episode;
|
episode: Episode;
|
||||||
|
spaceId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function SpaceEpisodeCard({ episode }: SpaceFactCardProps) {
|
export function SpaceEpisodeCard({ episode, spaceId }: SpaceFactCardProps) {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const formatDate = (date: Date | string) => {
|
const formatDate = (date: Date | string) => {
|
||||||
const d = new Date(date);
|
const d = new Date(date);
|
||||||
@ -62,6 +64,7 @@ export function SpaceEpisodeCard({ episode }: SpaceFactCardProps) {
|
|||||||
<Calendar className="h-3 w-3" />
|
<Calendar className="h-3 w-3" />
|
||||||
{formatDate(episode.validAt)}
|
{formatDate(episode.validAt)}
|
||||||
</Badge>
|
</Badge>
|
||||||
|
<SpaceEpisodeActions episodeId={episode.uuid} spaceId={spaceId} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -18,12 +18,14 @@ interface SpaceEpisodesListProps {
|
|||||||
loadMore: () => void;
|
loadMore: () => void;
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
height?: number;
|
height?: number;
|
||||||
|
spaceId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
function EpisodeItemRenderer(
|
function EpisodeItemRenderer(
|
||||||
props: ListRowProps,
|
props: ListRowProps,
|
||||||
episodes: Episode[],
|
episodes: Episode[],
|
||||||
cache: CellMeasurerCache,
|
cache: CellMeasurerCache,
|
||||||
|
spaceId: string,
|
||||||
) {
|
) {
|
||||||
const { index, key, style, parent } = props;
|
const { index, key, style, parent } = props;
|
||||||
const episode = episodes[index];
|
const episode = episodes[index];
|
||||||
@ -37,7 +39,7 @@ function EpisodeItemRenderer(
|
|||||||
rowIndex={index}
|
rowIndex={index}
|
||||||
>
|
>
|
||||||
<div key={key} style={style} className="pb-2">
|
<div key={key} style={style} className="pb-2">
|
||||||
<SpaceEpisodeCard episode={episode} />
|
<SpaceEpisodeCard episode={episode} spaceId={spaceId} />
|
||||||
</div>
|
</div>
|
||||||
</CellMeasurer>
|
</CellMeasurer>
|
||||||
);
|
);
|
||||||
@ -48,6 +50,7 @@ export function SpaceEpisodesList({
|
|||||||
hasMore,
|
hasMore,
|
||||||
loadMore,
|
loadMore,
|
||||||
isLoading,
|
isLoading,
|
||||||
|
spaceId,
|
||||||
}: SpaceEpisodesListProps) {
|
}: SpaceEpisodesListProps) {
|
||||||
// Create a CellMeasurerCache instance using useRef to prevent recreation
|
// Create a CellMeasurerCache instance using useRef to prevent recreation
|
||||||
const cacheRef = useRef<CellMeasurerCache | null>(null);
|
const cacheRef = useRef<CellMeasurerCache | null>(null);
|
||||||
@ -91,7 +94,7 @@ export function SpaceEpisodesList({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const rowRenderer = (props: ListRowProps) => {
|
const rowRenderer = (props: ListRowProps) => {
|
||||||
return EpisodeItemRenderer(props, episodes, cache);
|
return EpisodeItemRenderer(props, episodes, cache, spaceId);
|
||||||
};
|
};
|
||||||
|
|
||||||
const rowHeight = ({ index }: Index) => {
|
const rowHeight = ({ index }: Index) => {
|
||||||
|
|||||||
@ -37,7 +37,7 @@ export async function loader({ request, params }: LoaderFunctionArgs) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function Episodes() {
|
export default function Episodes() {
|
||||||
const { episodes } = useLoaderData<typeof loader>();
|
const { episodes, space } = useLoaderData<typeof loader>();
|
||||||
const [selectedValidDate, setSelectedValidDate] = useState<
|
const [selectedValidDate, setSelectedValidDate] = useState<
|
||||||
string | undefined
|
string | undefined
|
||||||
>();
|
>();
|
||||||
@ -98,6 +98,7 @@ export default function Episodes() {
|
|||||||
hasMore={false} // TODO: Implement real pagination
|
hasMore={false} // TODO: Implement real pagination
|
||||||
loadMore={loadMore}
|
loadMore={loadMore}
|
||||||
isLoading={false}
|
isLoading={false}
|
||||||
|
spaceId={space.id}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</ClientOnly>
|
</ClientOnly>
|
||||||
|
|||||||
@ -236,10 +236,6 @@ export class SpaceService {
|
|||||||
throw new Error("Space not found");
|
throw new Error("Space not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (space.name === "Profile") {
|
|
||||||
throw new Error("Cannot reset Profile space");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete all relationships in Neo4j (episodes, statements, etc.)
|
// Delete all relationships in Neo4j (episodes, statements, etc.)
|
||||||
await deleteSpace(spaceId, userId);
|
await deleteSpace(spaceId, userId);
|
||||||
|
|
||||||
|
|||||||
@ -198,7 +198,7 @@ async function generateSpaceSummary(
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
episodeDifference < CONFIG.summaryEpisodeThreshold ||
|
episodeDifference < CONFIG.summaryEpisodeThreshold ||
|
||||||
lastSummaryEpisodeCount === 0
|
lastSummaryEpisodeCount !== 0
|
||||||
) {
|
) {
|
||||||
logger.info(
|
logger.info(
|
||||||
`Skipping summary generation for space ${spaceId}: only ${episodeDifference} new episodes (threshold: ${CONFIG.summaryEpisodeThreshold})`,
|
`Skipping summary generation for space ${spaceId}: only ${episodeDifference} new episodes (threshold: ${CONFIG.summaryEpisodeThreshold})`,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user