From 733ffc5e0229f646855af4f1b8a77b511748325b Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Thu, 25 Jun 2026 18:07:40 +0300 Subject: [PATCH 1/3] PM-5308 - projects showcase posts table --- .../lib/hooks/useFetchSubmissionReviews.ts | 46 +- src/apps/work/src/config/routes.config.ts | 1 + .../ProjectListTabs/ProjectListTabs.spec.tsx | 1 + .../ProjectListTabs/ProjectListTabs.tsx | 8 + .../ProjectsShowcaseFilter.module.scss | 65 +++ .../ProjectsShowcaseFilter.tsx | 129 +++++ .../ProjectsShowcaseFilter/index.ts | 1 + src/apps/work/src/lib/components/index.ts | 1 + src/apps/work/src/lib/hooks/index.ts | 1 + .../lib/hooks/useFetchProjectShowcasePosts.ts | 133 +++++ .../lib/models/ProjectShowcasePost.model.ts | 45 ++ src/apps/work/src/lib/models/index.ts | 1 + src/apps/work/src/lib/services/index.ts | 1 + .../project-showcase-posts.service.ts | 177 ++++++ .../ProjectShowcasePage.module.scss | 120 +++++ .../ProjectShowcasePage.tsx | 502 ++++++++++++++++++ .../showcase/ProjectShowcasePage/index.ts | 3 + src/apps/work/src/pages/showcase/index.ts | 1 + src/apps/work/src/work-app.routes.tsx | 16 + 19 files changed, 1229 insertions(+), 23 deletions(-) create mode 100644 src/apps/work/src/lib/components/ProjectsShowcaseFilter/ProjectsShowcaseFilter.module.scss create mode 100644 src/apps/work/src/lib/components/ProjectsShowcaseFilter/ProjectsShowcaseFilter.tsx create mode 100644 src/apps/work/src/lib/components/ProjectsShowcaseFilter/index.ts create mode 100644 src/apps/work/src/lib/hooks/useFetchProjectShowcasePosts.ts create mode 100644 src/apps/work/src/lib/models/ProjectShowcasePost.model.ts create mode 100644 src/apps/work/src/lib/services/project-showcase-posts.service.ts create mode 100644 src/apps/work/src/pages/showcase/ProjectShowcasePage/ProjectShowcasePage.module.scss create mode 100644 src/apps/work/src/pages/showcase/ProjectShowcasePage/ProjectShowcasePage.tsx create mode 100644 src/apps/work/src/pages/showcase/ProjectShowcasePage/index.ts create mode 100644 src/apps/work/src/pages/showcase/index.ts diff --git a/src/apps/review/src/lib/hooks/useFetchSubmissionReviews.ts b/src/apps/review/src/lib/hooks/useFetchSubmissionReviews.ts index a89e0e930..751cbb2cd 100644 --- a/src/apps/review/src/lib/hooks/useFetchSubmissionReviews.ts +++ b/src/apps/review/src/lib/hooks/useFetchSubmissionReviews.ts @@ -799,29 +799,6 @@ export function useFetchSubmissionReviews(reviewId: string = ''): useFetchSubmis const listRequest: Promise< BackendReviewItem | BackendAppealResponse >[] = [] - if (updatedResponse) { - listRequest.push( - new Promise((resolve, reject) => { - updateReviewItem(reviewItem.id, { - finalAnswer: updatedResponse, - initialAnswer: reviewItem.initialAnswer ?? '', - scorecardQuestionId: reviewItem.scorecardQuestionId, - }) - .then(rs => { - setUpdatedReviewInfo(previousReviewInfo => applyAppealResponseScoreUpdate( - previousReviewInfo ?? reviewInfo, - reviewItem.id, - updatedResponse, - scorecardInfo, - )) - - resolve(rs) - }) - .catch(reject) - }), - ) - } - listRequest.push( new Promise((resolve, reject) => { const updateData = { @@ -855,6 +832,29 @@ export function useFetchSubmissionReviews(reviewId: string = ''): useFetchSubmis }), ) + if (updatedResponse) { + listRequest.push( + new Promise((resolve, reject) => { + updateReviewItem(reviewItem.id, { + finalAnswer: updatedResponse, + initialAnswer: reviewItem.initialAnswer ?? '', + scorecardQuestionId: reviewItem.scorecardQuestionId, + }) + .then(rs => { + setUpdatedReviewInfo(previousReviewInfo => applyAppealResponseScoreUpdate( + previousReviewInfo ?? reviewInfo, + reviewItem.id, + updatedResponse, + scorecardInfo, + )) + + resolve(rs) + }) + .catch(reject) + }), + ) + } + setIsSavingAppealResponse(true) Promise.all(listRequest) .then(async () => { diff --git a/src/apps/work/src/config/routes.config.ts b/src/apps/work/src/config/routes.config.ts index 91b847e7a..e9f8ed0ff 100644 --- a/src/apps/work/src/config/routes.config.ts +++ b/src/apps/work/src/config/routes.config.ts @@ -23,6 +23,7 @@ export const engagementAssignmentsRouteId = 'engagement-assignments' export const engagementFeedbackRouteId = 'engagement-feedback' export const engagementExperienceRouteId = 'engagement-experience' export const projectAssetsRouteId = 'project-assets' +export const projectShowcaseRouteId = 'project-showcase' export const usersRouteId = 'users' export const projectInvitationsRouteId = 'project-invitations' export const groupsRouteId = 'groups' diff --git a/src/apps/work/src/lib/components/ProjectListTabs/ProjectListTabs.spec.tsx b/src/apps/work/src/lib/components/ProjectListTabs/ProjectListTabs.spec.tsx index 585759bc1..9f13b5906 100644 --- a/src/apps/work/src/lib/components/ProjectListTabs/ProjectListTabs.spec.tsx +++ b/src/apps/work/src/lib/components/ProjectListTabs/ProjectListTabs.spec.tsx @@ -81,6 +81,7 @@ describe('ProjectListTabs', () => { ['/projects/200/engagements', 'Engagements'], ['/projects/200/users', 'Users'], ['/projects/200/assets', 'Assets Library'], + ['/projects/200/showcase', 'Showcase'], ])('marks the matching tab active for %s', (pathname: string, activeLabel: string) => { renderProjectListTabs(pathname) diff --git a/src/apps/work/src/lib/components/ProjectListTabs/ProjectListTabs.tsx b/src/apps/work/src/lib/components/ProjectListTabs/ProjectListTabs.tsx index f00defa2d..12b099638 100644 --- a/src/apps/work/src/lib/components/ProjectListTabs/ProjectListTabs.tsx +++ b/src/apps/work/src/lib/components/ProjectListTabs/ProjectListTabs.tsx @@ -43,11 +43,13 @@ export const ProjectListTabs: FC = (props: ProjectListTabs const engagementsPath = `/projects/${props.projectId}/engagements` const usersPath = `/projects/${props.projectId}/users` const assetsPath = `/projects/${props.projectId}/assets` + const showcasePath = `/projects/${props.projectId}/showcase` const isChallengesActive = isTabActive(pathname, challengesPath) const isEngagementsActive = isTabActive(pathname, engagementsPath) const isUsersActive = isTabActive(pathname, usersPath) const isAssetsActive = isTabActive(pathname, assetsPath) + const isShowcaseActive = isTabActive(pathname, showcasePath) const canViewEngagements = canViewAllEngagements(userRoles) const usersLinkState = isUsersActive ? undefined @@ -84,6 +86,12 @@ export const ProjectListTabs: FC = (props: ProjectListTabs > Assets Library + + Showcase + ) } diff --git a/src/apps/work/src/lib/components/ProjectsShowcaseFilter/ProjectsShowcaseFilter.module.scss b/src/apps/work/src/lib/components/ProjectsShowcaseFilter/ProjectsShowcaseFilter.module.scss new file mode 100644 index 000000000..3263408d4 --- /dev/null +++ b/src/apps/work/src/lib/components/ProjectsShowcaseFilter/ProjectsShowcaseFilter.module.scss @@ -0,0 +1,65 @@ +@import '@libs/ui/styles/includes'; + +.container { + display: grid; + gap: 16px; + grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); + margin-bottom: 20px; + margin-top: 8px; + padding: 20px; + border-radius: 8px; + background: rgba(224, 228, 232, 0.3); +} + +.filterField { + display: flex; + flex-direction: column; + gap: 8px; + + label { + color: $black-80; + font-size: 13px; + } +} + +.actions { + display: flex; + align-items: flex-end; + justify-self: start; +} + +.searchInputWrap { + display: flex; + align-items: center; + gap: 8px; + border: 1px solid $black-20; + border-radius: 4px; + background: #fff; + padding: 8px 12px; +} + +.searchIcon { + color: $black-60; + width: 18px; + height: 18px; +} + +.searchInput { + border: 0; + background: transparent; + color: $black-80; + outline: none; + width: 100%; +} + +@media (max-width: 1100px) { + .container { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } +} + +@media (max-width: 700px) { + .container { + grid-template-columns: 1fr; + } +} diff --git a/src/apps/work/src/lib/components/ProjectsShowcaseFilter/ProjectsShowcaseFilter.tsx b/src/apps/work/src/lib/components/ProjectsShowcaseFilter/ProjectsShowcaseFilter.tsx new file mode 100644 index 000000000..3bce1c37c --- /dev/null +++ b/src/apps/work/src/lib/components/ProjectsShowcaseFilter/ProjectsShowcaseFilter.tsx @@ -0,0 +1,129 @@ +import { ChangeEvent, FC, useMemo } from 'react' +import Select, { SingleValue } from 'react-select' + +import { + Button, + IconOutline, +} from '~/libs/ui' + +import styles from './ProjectsShowcaseFilter.module.scss' + +interface SelectOption { + label: string + value: string +} + +interface ProjectsShowcaseFilterProps { + keywordInput: string + selectedStatus?: SelectOption + selectedIndustry?: SelectOption + selectedCategory?: SelectOption + industryOptions: SelectOption[] + categoryOptions: SelectOption[] + isIndustriesLoading: boolean + isCategoriesLoading: boolean + onSearchInputChange: (event: ChangeEvent) => void + onStatusChange: (option: SingleValue) => void + onIndustryChange: (option: SingleValue) => void + onCategoryChange: (option: SingleValue) => void + onResetFilters: () => void +} + +const STATUS_OPTIONS: SelectOption[] = [ + { label: 'All statuses', value: '' }, + { label: 'Draft', value: 'DRAFT' }, + { label: 'Published', value: 'PUBLISHED' }, + { label: 'Archived', value: 'ARCHIVED' }, +] + +export const ProjectsShowcaseFilter: FC = (props: ProjectsShowcaseFilterProps) => { + const { + categoryOptions, + selectedCategory, + selectedIndustry, + selectedStatus, + industryOptions, + isCategoriesLoading, + isIndustriesLoading, + keywordInput, + onCategoryChange, + onIndustryChange, + onResetFilters, + onSearchInputChange, + onStatusChange, + } = props + + const statusOptions = useMemo(() => STATUS_OPTIONS, []) + + return ( +
+
+ +
+ + +
+
+ +
+ + +
+ +
+ +