295 lines
7.7 KiB
295 lines
7.7 KiB
import clsx from 'clsx';
|
|
import { useMemo, useState } from 'react';
|
|
import {
|
|
ChatBubbleBottomCenterTextIcon,
|
|
CheckIcon,
|
|
EyeIcon,
|
|
TrashIcon,
|
|
} from '@heroicons/react/24/outline';
|
|
import type { QuestionsQuestionType } from '@prisma/client';
|
|
import { Button } from '@tih/ui';
|
|
|
|
import { useProtectedCallback } from '~/utils/questions/useProtectedCallback';
|
|
import { useQuestionVote } from '~/utils/questions/useVote';
|
|
|
|
import AddToListDropdown from '../../AddToListDropdown';
|
|
import type { CreateQuestionEncounterData } from '../../forms/CreateQuestionEncounterForm';
|
|
import CreateQuestionEncounterForm from '../../forms/CreateQuestionEncounterForm';
|
|
import QuestionAggregateBadge from '../../QuestionAggregateBadge';
|
|
import QuestionTypeBadge from '../../QuestionTypeBadge';
|
|
import VotingButtons from '../../VotingButtons';
|
|
|
|
import type { CountryInfo } from '~/types/questions';
|
|
|
|
type UpvoteProps =
|
|
| {
|
|
showVoteButtons: true;
|
|
upvoteCount: number;
|
|
}
|
|
| {
|
|
showVoteButtons?: false;
|
|
upvoteCount?: never;
|
|
};
|
|
|
|
type DeleteProps =
|
|
| {
|
|
onDelete: () => void;
|
|
showDeleteButton: true;
|
|
}
|
|
| {
|
|
onDelete?: never;
|
|
showDeleteButton?: false;
|
|
};
|
|
|
|
type AnswerStatisticsProps =
|
|
| {
|
|
answerCount: number;
|
|
showAnswerStatistics: true;
|
|
}
|
|
| {
|
|
answerCount?: never;
|
|
showAnswerStatistics?: false;
|
|
};
|
|
|
|
type AggregateStatisticsProps =
|
|
| {
|
|
companies: Record<string, number>;
|
|
countries: Record<string, CountryInfo>;
|
|
roles: Record<string, number>;
|
|
showAggregateStatistics: true;
|
|
}
|
|
| {
|
|
companies?: never;
|
|
countries?: never;
|
|
roles?: never;
|
|
showAggregateStatistics?: false;
|
|
};
|
|
|
|
type ActionButtonProps =
|
|
| {
|
|
actionButtonLabel: string;
|
|
onActionButtonClick: () => void;
|
|
showActionButton: true;
|
|
}
|
|
| {
|
|
actionButtonLabel?: never;
|
|
onActionButtonClick?: never;
|
|
showActionButton?: false;
|
|
};
|
|
|
|
type ReceivedStatisticsProps =
|
|
| {
|
|
receivedCount: number;
|
|
showReceivedStatistics: true;
|
|
}
|
|
| {
|
|
receivedCount?: never;
|
|
showReceivedStatistics?: false;
|
|
};
|
|
|
|
type CreateEncounterProps =
|
|
| {
|
|
createEncounterButtonText: string;
|
|
onReceivedSubmit: (data: CreateQuestionEncounterData) => void;
|
|
showCreateEncounterButton: true;
|
|
}
|
|
| {
|
|
createEncounterButtonText?: never;
|
|
onReceivedSubmit?: never;
|
|
showCreateEncounterButton?: false;
|
|
};
|
|
|
|
type AddToListProps =
|
|
| {
|
|
showAddToList: true;
|
|
}
|
|
| {
|
|
showAddToList?: false;
|
|
};
|
|
|
|
export type BaseQuestionCardProps = ActionButtonProps &
|
|
AddToListProps &
|
|
AggregateStatisticsProps &
|
|
AnswerStatisticsProps &
|
|
CreateEncounterProps &
|
|
DeleteProps &
|
|
ReceivedStatisticsProps &
|
|
UpvoteProps & {
|
|
content: string;
|
|
questionId: string;
|
|
showHover?: boolean;
|
|
timestamp: string | null;
|
|
truncateContent?: boolean;
|
|
type: QuestionsQuestionType;
|
|
};
|
|
|
|
export default function BaseQuestionCard({
|
|
questionId,
|
|
companies,
|
|
answerCount,
|
|
content,
|
|
receivedCount,
|
|
type,
|
|
showVoteButtons,
|
|
showAggregateStatistics,
|
|
showAnswerStatistics,
|
|
showReceivedStatistics,
|
|
showCreateEncounterButton,
|
|
createEncounterButtonText,
|
|
showActionButton,
|
|
actionButtonLabel,
|
|
onActionButtonClick,
|
|
upvoteCount,
|
|
timestamp,
|
|
roles,
|
|
countries,
|
|
showHover,
|
|
onReceivedSubmit,
|
|
showDeleteButton,
|
|
showAddToList,
|
|
onDelete,
|
|
truncateContent = true,
|
|
}: BaseQuestionCardProps) {
|
|
const [showReceivedForm, setShowReceivedForm] = useState(false);
|
|
const { handleDownvote, handleUpvote, vote } = useQuestionVote(questionId);
|
|
const hoverClass = showHover ? 'hover:bg-slate-50' : '';
|
|
|
|
const locations = useMemo(() => {
|
|
if (countries === undefined) {
|
|
return undefined;
|
|
}
|
|
|
|
const countryCount: Record<string, number> = {};
|
|
// Decompose countries
|
|
for (const country of Object.keys(countries)) {
|
|
const { total } = countries[country];
|
|
countryCount[country] = total;
|
|
}
|
|
|
|
return countryCount;
|
|
}, [countries]);
|
|
|
|
const handleCreateEncounterClick = useProtectedCallback(() => {
|
|
setShowReceivedForm(true);
|
|
});
|
|
|
|
const cardContent = (
|
|
<>
|
|
{showVoteButtons && (
|
|
<VotingButtons
|
|
upvoteCount={upvoteCount}
|
|
vote={vote}
|
|
onDownvote={handleDownvote}
|
|
onUpvote={handleUpvote}
|
|
/>
|
|
)}
|
|
<div className="flex flex-1 flex-col items-start gap-2">
|
|
<div className="flex items-baseline justify-between self-stretch">
|
|
<div className="flex items-center gap-2 text-slate-500">
|
|
{showAggregateStatistics && (
|
|
<>
|
|
<QuestionTypeBadge type={type} />
|
|
<QuestionAggregateBadge
|
|
statistics={companies}
|
|
variant="primary"
|
|
/>
|
|
<QuestionAggregateBadge
|
|
statistics={locations!}
|
|
variant="success"
|
|
/>
|
|
<QuestionAggregateBadge statistics={roles} variant="danger" />
|
|
</>
|
|
)}
|
|
{timestamp !== null && <p className="text-xs">{timestamp}</p>}
|
|
{showAddToList && (
|
|
<div className="pl-4">
|
|
<AddToListDropdown questionId={questionId} />
|
|
</div>
|
|
)}
|
|
</div>
|
|
{showActionButton && (
|
|
<Button
|
|
label={actionButtonLabel}
|
|
size="sm"
|
|
variant="secondary"
|
|
onClick={onActionButtonClick}
|
|
/>
|
|
)}
|
|
</div>
|
|
<p className={clsx(truncateContent && 'line-clamp-2 text-ellipsis')}>
|
|
{content}
|
|
</p>
|
|
{!showReceivedForm &&
|
|
(showAnswerStatistics ||
|
|
showReceivedStatistics ||
|
|
showCreateEncounterButton) && (
|
|
<div className="flex gap-2">
|
|
{showAnswerStatistics && (
|
|
<Button
|
|
addonPosition="start"
|
|
icon={ChatBubbleBottomCenterTextIcon}
|
|
label={`${answerCount} answers`}
|
|
size="sm"
|
|
variant="tertiary"
|
|
/>
|
|
)}
|
|
{showReceivedStatistics && (
|
|
<Button
|
|
addonPosition="start"
|
|
icon={EyeIcon}
|
|
label={`${receivedCount} received this`}
|
|
size="sm"
|
|
variant="tertiary"
|
|
/>
|
|
)}
|
|
{showCreateEncounterButton && (
|
|
<Button
|
|
addonPosition="start"
|
|
icon={CheckIcon}
|
|
label={createEncounterButtonText}
|
|
size="sm"
|
|
variant="tertiary"
|
|
onClick={handleCreateEncounterClick}
|
|
/>
|
|
)}
|
|
</div>
|
|
)}
|
|
{showReceivedForm && (
|
|
<CreateQuestionEncounterForm
|
|
onCancel={() => {
|
|
setShowReceivedForm(false);
|
|
}}
|
|
onSubmit={(data) => {
|
|
onReceivedSubmit?.(data);
|
|
setShowReceivedForm(false);
|
|
}}
|
|
/>
|
|
)}
|
|
</div>
|
|
</>
|
|
);
|
|
|
|
return (
|
|
<article
|
|
className={`group flex gap-4 rounded-md border border-slate-300 bg-white p-4 ${hoverClass}`}>
|
|
{cardContent}
|
|
{showDeleteButton && (
|
|
<div className="fill-danger-700 invisible self-center group-hover:visible">
|
|
<Button
|
|
icon={TrashIcon}
|
|
isLabelHidden={true}
|
|
label="Delete"
|
|
size="md"
|
|
variant="tertiary"
|
|
onClick={(e) => {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
onDelete();
|
|
}}
|
|
/>
|
|
</div>
|
|
)}
|
|
</article>
|
|
);
|
|
}
|