Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,11 @@ import {
IconOutline,
} from '~/libs/ui'

import { FormSelectOption } from '../form'

import styles from './ProjectsShowcaseFilter.module.scss'

interface SelectOption {
label: string
value: string
}
type SelectOption = FormSelectOption

interface ProjectsShowcaseFilterProps {
keywordInput: string
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,20 @@ export const FormSelectField: FC<FormSelectFieldProps> = (props: FormSelectField
? props.fromFieldValue(field.value, options)
: defaultFromFieldValue(field.value, options, isMulti)

const selectStyles = useMemo(
() => ({
menu: (provided: Record<string, unknown>) => ({
...provided,
zIndex: 9999,
}),
menuPortal: (provided: Record<string, unknown>) => ({
...provided,
zIndex: 9999,
}),
}),
[],
)

const handleSelectChange = useCallback(
(selectedValue: SelectValue): void => {
const normalizedValue = props.toFieldValue
Expand Down Expand Up @@ -198,6 +212,7 @@ export const FormSelectField: FC<FormSelectFieldProps> = (props: FormSelectField
isMulti={isMulti}
loadOptions={props.loadOptions}
menuPortalTarget={menuPortalTarget}
styles={selectStyles}
onBlur={field.onBlur}
onChange={handleSelectChange}
options={options}
Expand Down
3 changes: 3 additions & 0 deletions src/apps/work/src/lib/models/ProjectShowcasePost.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ export interface ProjectShowcasePostTaxonomyItem {
export interface ProjectShowcasePost {
id: string
title: string
content?: string
status: string
projectId?: string
challengeIds?: string[]
createdAt: string
createdById: number
createdByHandle?: string
Expand Down
173 changes: 173 additions & 0 deletions src/apps/work/src/lib/services/project-showcase-posts.service.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import {
xhrGetAsync,
xhrGetPaginatedAsync,
xhrPatchAsync,
xhrPostAsync,
} from '~/libs/core'

import {
Expand All @@ -13,6 +15,7 @@ import type {
ProjectShowcasePost,
ProjectShowcasePostCategory,
ProjectShowcasePostIndustry,
ProjectShowcasePostTaxonomyItem,
} from '../models'

import { fetchMembersByUserIds } from './members.service'
Expand Down Expand Up @@ -151,6 +154,136 @@ export async function fetchProjectShowcasePosts(
}
}

function normalizeTaxonomyArray(value: unknown): ProjectShowcasePostTaxonomyItem[] {
if (!Array.isArray(value)) {
return []
}

return value.map((item: any) => ({
id: String(item?.id || ''),
name: String(item?.name || ''),
}))
}

function normalizeString(value: unknown): string {
return value !== undefined && value !== null
? String(value)
: ''
}

function normalizeStringOrUndefined(value: unknown): string | undefined {
return value !== undefined && value !== null
? String(value)
: undefined
}

function normalizeProjectShowcasePost(value: unknown): ProjectShowcasePost | undefined {
if (typeof value !== 'object' || value === null) {
return undefined
}

const post = value as Record<string, unknown>

return {
categories: normalizeTaxonomyArray(post.categories),
challengeIds: Array.isArray(post.challengeIds)
? post.challengeIds.map((item: any) => String(item))
: [],
content: normalizeStringOrUndefined(post.content),
createdAt: normalizeString(post.createdAt),
createdByHandle: normalizeStringOrUndefined(post.createdByHandle),
createdById: Number(post.createdById || 0),
id: normalizeString(post.id),
industries: normalizeTaxonomyArray(post.industries),
projectId: normalizeStringOrUndefined(post.projectId),
status: normalizeString(post.status),
title: normalizeString(post.title),
}
}

export async function fetchProjectShowcasePost(
projectId: string,
postId: string,
): Promise<ProjectShowcasePost> {
try {
const response = await xhrGetAsync<unknown>(
`${PROJECTS_API_URL}/${encodeURIComponent(projectId)}/posts/${encodeURIComponent(postId)}`,
)

const normalized = normalizeProjectShowcasePost(response)
if (!normalized) {
throw new Error('Failed to normalize showcase post')
}

return normalized
} catch (error) {
throw normalizeError(error, 'Failed to fetch showcase post')
}
}

export async function createProjectShowcasePost(
projectId: string,
payload: {
title: string
content: string
industryIds: string[]
categoryIds: string[]
challengeIds?: string[]
},
): Promise<ProjectShowcasePost> {
try {
const response = await xhrPostAsync<typeof payload, unknown>(
`${PROJECTS_API_URL}/${encodeURIComponent(projectId)}/posts`,
payload,
)

const normalized = normalizeProjectShowcasePost(response)
if (!normalized) {
throw new Error('Failed to normalize showcase post')
}

return normalized
} catch (error) {
throw normalizeError(error, 'Failed to create showcase post')
}
}

export async function updateProjectShowcasePost(
projectId: string,
postId: string,
payload: {
title?: string
content?: string
industryIds?: string[]
categoryIds?: string[]
challengeIds?: string[]
status?: string
},
): Promise<ProjectShowcasePost> {
try {
const response = await xhrPatchAsync<typeof payload, unknown>(
`${PROJECTS_API_URL}/${encodeURIComponent(projectId)}/posts/${encodeURIComponent(postId)}`,
payload,
)

const normalized = normalizeProjectShowcasePost(response)
if (!normalized) {
throw new Error('Failed to normalize showcase post')
}

return normalized
} catch (error) {
throw normalizeError(error, 'Failed to update showcase post')
}
}

export async function archiveProjectShowcasePost(
projectId: string,
postId: string,
): Promise<ProjectShowcasePost> {
return updateProjectShowcasePost(projectId, postId, { status: 'ARCHIVED' })
}

function normalizeTaxonomyItem(value: unknown): ProjectShowcasePostCategory | undefined {
if (typeof value !== 'object' || value === null) {
return undefined
Expand All @@ -172,6 +305,46 @@ function normalizeTaxonomyItem(value: unknown): ProjectShowcasePostCategory | un
return { id, name: trimmedName }
}

export async function createProjectShowcasePostIndustry(
name: string,
): Promise<ProjectShowcasePostIndustry> {
try {
const response = await xhrPostAsync<{ name: string }, unknown>(
`${PROJECTS_API_URL}/posts/industries`,
{ name },
)

const normalized = normalizeTaxonomyItem(response)
if (!normalized) {
throw new Error('Failed to normalize showcase post industry')
}

return normalized
} catch (error) {
throw normalizeError(error, 'Failed to create showcase post industry')
}
}

export async function createProjectShowcasePostCategory(
name: string,
): Promise<ProjectShowcasePostCategory> {
try {
const response = await xhrPostAsync<{ name: string }, unknown>(
`${PROJECTS_API_URL}/posts/categories`,
{ name },
)

const normalized = normalizeTaxonomyItem(response)
if (!normalized) {
throw new Error('Failed to normalize showcase post category')
}

return normalized
} catch (error) {
throw normalizeError(error, 'Failed to create showcase post category')
}
}

function sortTaxonomyItems<T extends ProjectShowcasePostCategory>(items: T[]): T[] {
return items
.slice()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,6 @@
width: 100%;
}

.actions {
display: flex;
justify-content: flex-start;
}

.errorBanner {
max-width: 100%;
}
Expand Down Expand Up @@ -129,6 +124,46 @@
}
}

.rowActions {
display: flex;
align-items: center;
gap: 10px;
}

.actionButton {
color: $link-blue-dark;
font-size: 12px;
font-weight: 700;
background: transparent;
border: 0;
cursor: pointer;
padding: 0;
text-decoration: none;

&:hover,
&:focus {
text-decoration: underline;
outline: none;
}
}

.actionDelete {
color: #9f1f1f;
}

.modalBody {
display: flex;
flex-direction: column;
gap: 16px;
}

.modalFooter {
margin-top: 24px;
display: flex;
justify-content: flex-end;
gap: 8px;
}


.loadingRow,
.emptyRow {
Expand Down
Loading
Loading