[web] interviews/qns: update topics

This commit is contained in:
Yangshun 2024-11-16 09:33:56 +08:00
parent fcbbeda777
commit da4ed17781
8 changed files with 169 additions and 109 deletions

View File

@ -171,23 +171,28 @@ function getQuestionBankSectionData(
ts: createQuestionData(language.ts),
};
const topicQuestionsData: Record<
QuestionTopic,
{
count: number;
duration: number;
questions: ReadonlyArray<QuestionMetadata>;
}
const topicQuestionsData: Partial<
Record<
QuestionTopic,
{
count: number;
duration: number;
questions: ReadonlyArray<QuestionMetadata>;
}
>
> = {
a11y: createQuestionData(topicQuestions.a11y),
async: createQuestionData(topicQuestions.async),
closure: createQuestionData(topicQuestions.closure),
css: createQuestionData(topicQuestions.css),
html: createQuestionData(topicQuestions.html),
i18n: createQuestionData(topicQuestions.i18n),
javascript: createQuestionData(topicQuestions.javascript),
network: createQuestionData(topicQuestions.network),
networking: createQuestionData(topicQuestions.networking),
oop: createQuestionData(topicQuestions.oop),
performance: createQuestionData(topicQuestions.performance),
security: createQuestionData(topicQuestions.security),
testing: createQuestionData(topicQuestions.testing),
'web-api': createQuestionData(topicQuestions['web-api']),
};
const formatQuestionsData: Record<

View File

@ -39,6 +39,7 @@ import {
} from '~/components/ui/theme';
import useQuestionFormatFilter from '../questions/listings/filters/hooks/useQuestionFormatFilter';
import useQuestionTopicLabels from '../questions/listings/filters/useQuestionTopicLabels';
type FilterType = 'format' | 'framework' | 'topics';
@ -52,23 +53,43 @@ export type QuestionBankDataType = Readonly<{
format: Record<QuestionFormat, QuestionDataType>;
framework: Record<QuestionFramework, QuestionDataType>;
language: Record<QuestionLanguage, QuestionDataType>;
topic: Record<QuestionTopic, QuestionDataType>;
topic: Partial<Record<QuestionTopic, QuestionDataType>>;
}>;
type Props = Readonly<{
questions: QuestionBankDataType;
}>;
const topicRoute: Record<QuestionTopic, string> = {
a11y: '/questions/quiz',
const selectedTopics = [
'a11y',
'async',
'css',
'closure',
'html',
'i18n',
'javascript',
'networking',
'oop',
'performance',
'security',
'web-api',
] as const;
type QuestionTopicToDisplay = (typeof selectedTopics)[number];
const topicHrefs: Record<QuestionTopicToDisplay, string> = {
a11y: '/questions/user-interface',
async: '/questions/quiz',
closure: '/questions/closure',
css: '/questions/css',
html: '/questions/html',
i18n: '/questions/quiz',
javascript: '/questions/js',
network: '/questions/quiz',
javascript: '/questions/javascript',
networking: '/questions/quiz',
oop: '/questions/quiz',
performance: '/questions/quiz',
security: '/questions/quiz',
testing: '/questions/quiz',
'web-api': '/questions/quiz',
};
const MAX_TO_SHOW = 4;
@ -78,12 +99,13 @@ export default function InterviewsMarketingPracticeQuestionBankSection({
}: Props) {
const intl = useIntl();
const topics = useQuestionTopicLabels();
const [selectedFilter, setSelectedFilter] = useState<FilterType>('topics');
const [formatFilters, formatFilterOptions] = useQuestionFormatFilter({
initialValue: ['javascript'],
});
const [topicFilters, topicFilterOptions] = useQuestionTopicFilter({
initialValue: ['javascript'],
initialValue: ['a11y'],
});
const [languageFilters, languageFilterOptions] = useQuestionLanguageFilter({
initialValue: ['html'],
@ -154,7 +176,10 @@ export default function InterviewsMarketingPracticeQuestionBankSection({
value: selectedFramework || selectedLanguage,
},
topics: {
items: topicFilterOptions.options,
items: selectedTopics.map((topic) => ({
label: topics[topic].label,
value: topic,
})),
onClick: (value: string) =>
topicFilterOptions.setValues(new Set([value]) as Set<QuestionTopic>),
value: selectedTopic,
@ -171,7 +196,9 @@ export default function InterviewsMarketingPracticeQuestionBankSection({
const selectedRoute = (() => {
switch (selectedFilter) {
case 'topics': {
return topicRoute[selectedTopic];
return (
topicHrefs[selectedTopic as QuestionTopicToDisplay] ?? '/questions'
);
}
case 'framework': {
return (
@ -193,7 +220,7 @@ export default function InterviewsMarketingPracticeQuestionBankSection({
return framework[selectedFramework] || language[selectedLanguage];
}
case 'topics': {
return topic[selectedTopic];
return topic[selectedTopic] ?? { count: 0, duration: 0, questions: [] };
}
}
}

View File

@ -161,14 +161,22 @@ export type QuestionMetadataWithCompletedStatus = QuestionMetadata & {
export type QuestionTopic =
| 'a11y'
| 'async'
| 'browser'
| 'closure'
| 'css'
| 'graph'
| 'html'
| 'i18n'
| 'javascript'
| 'network'
| 'networking'
| 'oop'
| 'performance'
| 'recursion'
| 'security'
| 'testing';
| 'testing'
| 'tree'
| 'web-api';
export type QuestionQuiz = QuestionBase;

View File

@ -14,14 +14,22 @@ import type { QuestionFilter } from '../QuestionFilterType';
// The lower the earlier it appears.
const topicRanks: Record<QuestionTopic, number> = {
a11y: 3,
async: 0,
browser: 0,
closure: 0,
css: 1,
graph: 0,
html: 2,
i18n: 40,
javascript: 0,
network: 60,
networking: 60,
oop: 0,
performance: 50,
recursion: 0,
security: 80,
testing: 99,
tree: 0,
'web-api': 0,
};
type Props = Readonly<{
@ -77,7 +85,6 @@ export default function useQuestionTopicFilter(
topicRanks[a as QuestionTopic] - topicRanks[b as QuestionTopic],
)
.map((topic) => ({
icon: topicLabels[topic as QuestionTopic].icon,
label: topicLabels[topic as QuestionTopic].label,
value: topic as QuestionTopic,
})),

View File

@ -1,15 +1,3 @@
import { BiUniversalAccess } from 'react-icons/bi';
import {
RiCss3Fill,
RiDashboard2Line,
RiHtml5Fill,
RiJavascriptFill,
RiLock2Line,
RiTestTubeLine,
RiTranslate2,
RiWifiLine,
} from 'react-icons/ri';
import { useIntl } from '~/components/intl';
import type { QuestionTopic } from '../../common/QuestionsTypes';
@ -20,80 +8,114 @@ export default function useQuestionTopicLabels() {
const topicTitles: Record<
QuestionTopic,
Readonly<{
icon: (props: React.ComponentProps<'svg'>) => JSX.Element;
label: string;
}>
> = {
a11y: {
icon: BiUniversalAccess,
label: intl.formatMessage({
defaultMessage: 'Accessibility',
description: 'Accessibility topic for quiz questions',
id: 'q0+3Lk',
description: 'Front end development topic',
id: 'TbZTWa',
}),
},
async: {
label: intl.formatMessage({
defaultMessage: 'Async',
description: 'Front end development topic',
id: 'ezEYel',
}),
},
browser: {
label: intl.formatMessage({
defaultMessage: 'Browser',
description: 'Front end development topic',
id: '+4J3PK',
}),
},
closure: {
label: intl.formatMessage({
defaultMessage: 'Closure',
description: 'Front end development topic',
id: 'MMLnqG',
}),
},
css: {
icon: RiCss3Fill,
label: 'CSS',
},
graph: {
label: intl.formatMessage({
defaultMessage: 'CSS',
description: 'CSS topic for quiz questions',
id: 'P4Or/u',
defaultMessage: 'Graph',
description: 'Front end development topic',
id: 'F8ULPJ',
}),
},
html: {
icon: RiHtml5Fill,
label: intl.formatMessage({
defaultMessage: 'HTML',
description: 'HTML topic for quiz questions',
id: 'Yb2e9Q',
}),
label: 'HTML',
},
i18n: {
icon: RiTranslate2,
label: intl.formatMessage({
defaultMessage: 'Internationalization',
description: 'Internationalization topic for quiz questions',
id: 'tonRki',
description: 'Front end development topic',
id: 'Oykw81',
}),
},
javascript: {
icon: RiJavascriptFill,
label: 'JavaScript',
},
networking: {
label: intl.formatMessage({
defaultMessage: 'JavaScript',
description: 'JavaScript topic for quiz questions',
id: 'w22UH7',
defaultMessage: 'Networking',
description: 'Front end development topic',
id: 'ZutazH',
}),
},
network: {
icon: RiWifiLine,
oop: {
label: intl.formatMessage({
defaultMessage: 'Network',
description: 'Network topic for quiz questions',
id: 'pM/ZPq',
defaultMessage: 'Object-oriented programming',
description: 'Front end development topic',
id: 'KLoXYT',
}),
},
performance: {
icon: RiDashboard2Line,
label: intl.formatMessage({
defaultMessage: 'Performance',
description: 'Performance topic for quiz questions',
id: 'kwblYW',
description: 'Front end development topic',
id: 'd1LTa+',
}),
},
recursion: {
label: intl.formatMessage({
defaultMessage: 'Recursion',
description: 'Front end development topic',
id: 'trRb9Z',
}),
},
security: {
icon: RiLock2Line,
label: intl.formatMessage({
defaultMessage: 'Security',
description: 'Security topic for quiz questions',
id: 'kvLlxS',
description: 'Front end development topic',
id: '4MzF6x',
}),
},
testing: {
icon: RiTestTubeLine,
label: intl.formatMessage({
defaultMessage: 'Testing',
description: 'Testing topic for quiz questions',
id: 'l9OWsu',
description: 'Front end development topic',
id: 'QwoKre',
}),
},
tree: {
label: intl.formatMessage({
defaultMessage: 'Tree',
description: 'Front end development topic',
id: 'Rs1Xi9',
}),
},
'web-api': {
label: intl.formatMessage({
defaultMessage: 'Web APIs',
description: 'Front end development topic',
id: 'a1RZkG',
}),
},
};

View File

@ -8,16 +8,24 @@ import useQuestionTopicLabels from '../listings/filters/useQuestionTopicLabels';
const TopicLabelClasses: Record<QuestionTopic, string> = {
a11y: 'bg-pink-500 text-white dark:bg-neutral-800 dark:text-pink-500',
async: '',
browser: '',
closure: '',
css: 'bg-sky-500 text-white dark:bg-neutral-800 dark:text-sky-500',
graph: '',
html: 'bg-orange-600 text-white dark:bg-neutral-800 dark:text-orange-600',
i18n: 'bg-neutral-700 text-white dark:bg-neutral-800 dark:text-neutral-300',
javascript:
'bg-yellow-500 text-black dark:bg-neutral-800 dark:text-yellow-500',
network: 'bg-teal-400 text-white dark:bg-neutral-800 dark:text-teal-400',
networking: 'bg-teal-400 text-white dark:bg-neutral-800 dark:text-teal-400',
oop: '',
performance:
'bg-indigo-500 text-white dark:bg-neutral-800 dark:text-indigo-400',
recursion: '',
security: 'bg-red text-white dark:bg-neutral-800 dark:text-red',
testing: 'bg-green-dark text-white dark:bg-neutral-800 dark:text-green',
tree: '',
'web-api': '',
};
export default function QuestionTopicLabel({

View File

@ -192,7 +192,7 @@ export function useQuestionFormatsData(): QuestionFormatData {
'a11y',
'i18n',
'css',
'network',
'networking',
'security',
'testing',
],

View File

@ -408,54 +408,37 @@ export function categorizeQuestionsByTopic(
quizQuestions: ReadonlyArray<QuestionMetadata>;
}>,
): Record<QuestionTopic, ReadonlyArray<QuestionMetadata>> {
const categorizedQuestions: Record<
QuestionTopic,
ReadonlyArray<QuestionMetadata>
> = {
const categorizedQuestions: Record<QuestionTopic, Array<QuestionMetadata>> = {
a11y: [],
async: [],
browser: [],
closure: [],
css: [],
graph: [],
html: [],
i18n: [],
javascript: [],
network: [],
networking: [],
oop: [],
performance: [],
recursion: [],
security: [],
testing: [],
tree: [],
'web-api': [],
};
const { codingQuestions, quizQuestions } = questions;
const LANGUAGE_TO_TOPIC: Record<string, QuestionTopic> = {
css: 'css',
html: 'html',
js: 'javascript',
};
function categorize(question: QuestionMetadata, type: 'coding' | 'quiz') {
if (type === 'coding') {
question.languages.forEach((language) => {
const topic = LANGUAGE_TO_TOPIC[language];
if (topic) {
categorizedQuestions[topic] = [
...categorizedQuestions[topic],
question,
];
}
});
} else {
question.topics.forEach((topic) => {
if (categorizedQuestions[topic]) {
categorizedQuestions[topic] = [
...categorizedQuestions[topic],
question,
];
}
});
}
}
// Process all coding and quiz questions in a single pass
codingQuestions.forEach((question) => categorize(question, 'coding'));
quizQuestions.forEach((question) => categorize(question, 'quiz'));
codingQuestions.forEach((question) => {
question.topics.forEach((topic) => {
categorizedQuestions[topic].push(question);
});
});
quizQuestions.forEach((question) => {
question.topics.forEach((topic) => {
categorizedQuestions[topic].push(question);
});
});
return categorizedQuestions;
}