Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WEB-447] feat: projects archive. #4014

Merged
merged 13 commits into from
Mar 21, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 6 additions & 2 deletions apiserver/plane/app/views/project/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -372,7 +372,7 @@ def partial_update(self, request, slug, pk=None):
return Response(
{"error": "Archived projects cannot be updated"},
status=status.HTTP_400_BAD_REQUEST,
)
)

serializer = ProjectSerializer(
project,
Expand Down Expand Up @@ -433,11 +433,15 @@ class ProjectArchiveUnarchiveEndpoint(BaseAPIView):
permission_classes = [
ProjectBasePermission,
]

def post(self, request, slug, project_id):
project = Project.objects.get(pk=project_id, workspace__slug=slug)
project.archived_at = timezone.now()
project.save()
return Response(status=status.HTTP_204_NO_CONTENT)
return Response(
{"archived_at": str(project.archived_at)},
status=status.HTTP_200_OK,
)

def delete(self, request, slug, project_id):
project = Project.objects.get(pk=project_id, workspace__slug=slug)
Expand Down
5 changes: 5 additions & 0 deletions packages/types/src/project/project_filters.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,14 @@ export type TProjectOrderByOptions =

export type TProjectDisplayFilters = {
my_projects?: boolean;
archived_projects?: boolean;
order_by?: TProjectOrderByOptions;
};

export type TProjectAppliedDisplayFilterKeys =
| "my_projects"
| "archived_projects";

export type TProjectFilters = {
access?: string[] | null;
lead?: string[] | null;
Expand Down
1 change: 1 addition & 0 deletions packages/types/src/project/projects.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export type TProjectLogoProps = {

export interface IProject {
archive_in: number;
archived_at: Date | null;
prateekshourya29 marked this conversation as resolved.
Show resolved Hide resolved
archived_issues: number;
archived_sub_issues: number;
close_in: number;
Expand Down
1 change: 1 addition & 0 deletions web/components/project/applied-filters/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export * from "./access";
export * from "./date";
export * from "./members";
export * from "./project-display-filters";
export * from "./root";
39 changes: 39 additions & 0 deletions web/components/project/applied-filters/project-display-filters.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { observer } from "mobx-react-lite";
// icons
import { X } from "lucide-react";
// types
import { TProjectAppliedDisplayFilterKeys } from "@plane/types";
// constants
import { PROJECT_DISPLAY_FILTER_OPTIONS } from "@/constants/project";

type Props = {
handleRemove: (key: TProjectAppliedDisplayFilterKeys) => void;
values: TProjectAppliedDisplayFilterKeys[];
editable: boolean | undefined;
};

export const AppliedProjectDisplayFilters: React.FC<Props> = observer((props) => {
const { handleRemove, values, editable } = props;

return (
<>
{values.map((key) => {
const filterLabel = PROJECT_DISPLAY_FILTER_OPTIONS.find((s) => s.key === key)?.label;
return (
<div key={key} className="flex items-center gap-1 rounded p-1 text-xs bg-custom-background-80">
{filterLabel}
{editable && (
<button
type="button"
className="grid place-items-center text-custom-text-300 hover:text-custom-text-200"
onClick={() => handleRemove(key)}
>
<X size={10} strokeWidth={2} />
</button>
)}
</div>
);
})}
</>
);
});
40 changes: 33 additions & 7 deletions web/components/project/applied-filters/root.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,24 @@
import { X } from "lucide-react";
import { TProjectFilters } from "@plane/types";
// components
import { Tooltip } from "@plane/ui";
import { AppliedAccessFilters, AppliedDateFilters, AppliedMembersFilters } from "@/components/project";
// types
import { TProjectAppliedDisplayFilterKeys, TProjectFilters } from "@plane/types";
// ui
import { Tooltip } from "@plane/ui";
// components
import {
AppliedAccessFilters,
AppliedDateFilters,
AppliedMembersFilters,
AppliedProjectDisplayFilters,
} from "@/components/project";
// helpers
import { replaceUnderscoreIfSnakeCase } from "@/helpers/string.helper";
// types

type Props = {
appliedFilters: TProjectFilters;
appliedDisplayFilters: TProjectAppliedDisplayFilterKeys[];
handleClearAllFilters: () => void;
handleRemoveFilter: (key: keyof TProjectFilters, value: string | null) => void;
handleRemoveDisplayFilter: (key: TProjectAppliedDisplayFilterKeys) => void;
alwaysAllowEditing?: boolean;
filteredProjects: number;
totalProjects: number;
Expand All @@ -23,21 +30,24 @@ const DATE_FILTERS = ["created_at"];
export const ProjectAppliedFiltersList: React.FC<Props> = (props) => {
const {
appliedFilters,
appliedDisplayFilters,
handleClearAllFilters,
handleRemoveFilter,
handleRemoveDisplayFilter,
alwaysAllowEditing,
filteredProjects,
totalProjects,
} = props;

if (!appliedFilters) return null;
if (Object.keys(appliedFilters).length === 0) return null;
if (!appliedFilters && !appliedDisplayFilters) return null;
if (Object.keys(appliedFilters).length === 0 && appliedDisplayFilters.length === 0) return null;

const isEditingAllowed = alwaysAllowEditing;

return (
<div className="flex items-start justify-between gap-1.5">
<div className="flex flex-wrap items-stretch gap-2 bg-custom-background-100">
{/* Applied filters */}
{Object.entries(appliedFilters).map(([key, value]) => {
const filterKey = key as keyof TProjectFilters;

Expand Down Expand Up @@ -85,6 +95,22 @@ export const ProjectAppliedFiltersList: React.FC<Props> = (props) => {
</div>
);
})}
{/* Applied display filters */}
{appliedDisplayFilters.length > 0 && (
<div
key="project_display_filters"
className="flex flex-wrap items-center gap-2 rounded-md border border-custom-border-200 px-2 py-1 capitalize"
>
<div className="flex flex-wrap items-center gap-1.5">
<span className="text-xs text-custom-text-300">Projects</span>
<AppliedProjectDisplayFilters
editable={isEditingAllowed}
values={appliedDisplayFilters}
handleRemove={(key) => handleRemoveDisplayFilter(key)}
/>
</div>
</div>
)}
{isEditingAllowed && (
<button
type="button"
Expand Down
4 changes: 2 additions & 2 deletions web/components/project/card-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ export const ProjectCardList = observer(() => {
// store hooks
const { commandPalette: commandPaletteStore } = useApplication();
const { setTrackElement } = useEventTracker();
const { workspaceProjectIds, filteredProjectIds, getProjectById } = useProject();
const { totalProjectIds, filteredProjectIds, getProjectById } = useProject();
const { searchQuery } = useProjectFilter();

if (workspaceProjectIds?.length === 0)
if (totalProjectIds?.length === 0)
return (
<EmptyState
type={EmptyStateType.WORKSPACE_PROJECTS}
Expand Down