Shih-hungg commited on
Commit
529c982
·
1 Parent(s): afdd8ce

Refactor question creation card

Browse files
src/app/page.tsx CHANGED
@@ -2,7 +2,7 @@
2
 
3
  import { useState } from 'react';
4
  import { useChat } from '@ai-sdk/react';
5
- import QuestionParameterForm from '@/components/QuestionParameterForm';
6
  import { QuestionType, QuestionParameters, GeneratedQuestion } from '@/types/quiz';
7
 
8
  const QUESTION_TYPES: QuestionType[] = [
 
2
 
3
  import { useState } from 'react';
4
  import { useChat } from '@ai-sdk/react';
5
+ import { QuestionParameterForm } from '@/components/question-creation-card';
6
  import { QuestionType, QuestionParameters, GeneratedQuestion } from '@/types/quiz';
7
 
8
  const QUESTION_TYPES: QuestionType[] = [
src/components/ParameterInput.tsx CHANGED
@@ -1,93 +1,84 @@
1
  'use client';
2
 
3
  import { ParameterConfig } from '@/types/questionConfig';
 
4
 
5
  interface ParameterInputProps {
6
  config: ParameterConfig;
7
  value: any;
8
  onChange: (value: any) => void;
 
 
9
  }
10
 
11
- export default function ParameterInput({ config, value, onChange }: ParameterInputProps) {
12
- const { key, label, type, options, min, max, step, placeholder, helpText } = config;
13
-
14
- const currentValue = value ?? config.defaultValue;
15
-
16
- const baseInputClasses = "w-full p-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500";
 
 
17
 
18
  switch (type) {
19
  case 'select':
20
  return (
21
- <div>
22
- <label className="block text-sm font-medium mb-2">{label}</label>
23
- <select
24
- value={currentValue}
25
- onChange={(e) => {
26
- const val = options?.find(opt => opt.value.toString() === e.target.value)?.value;
27
- onChange(val);
28
- }}
29
- className={baseInputClasses}
30
- >
31
- {options?.map((option) => (
32
- <option key={option.value.toString()} value={option.value.toString()}>
33
- {option.label}
34
- </option>
35
- ))}
36
- </select>
37
- </div>
38
  );
39
 
40
  case 'range':
41
  return (
42
- <div>
43
- <label className="block text-sm font-medium mb-2">{label}</label>
44
- <input
45
- type="range"
46
- min={min}
47
- max={max}
48
- step={step}
49
- value={currentValue}
50
- onChange={(e) => onChange(parseInt(e.target.value))}
51
- className="w-full"
52
- />
53
- <div className="text-center text-sm text-gray-600">
54
- {currentValue} {helpText}
55
- </div>
56
- </div>
57
  );
58
 
59
  case 'input':
60
  return (
61
- <div>
62
- <label className="block text-sm font-medium mb-2">{label}</label>
63
- <input
64
- type="text"
65
- value={currentValue || ''}
66
- placeholder={placeholder}
67
- onChange={(e) => onChange(e.target.value)}
68
- className={baseInputClasses}
69
- />
70
- {helpText && (
71
- <div className="text-xs text-gray-500 mt-1">{helpText}</div>
72
- )}
73
- </div>
74
  );
75
 
76
  case 'checkbox':
77
  return (
78
- <div className="flex items-center">
79
- <input
80
- type="checkbox"
81
- id={key}
82
- checked={currentValue || false}
83
- onChange={(e) => onChange(e.target.checked)}
84
- className="mr-2"
85
- />
86
- <label htmlFor={key} className="text-sm font-medium">{label}</label>
87
- {helpText && (
88
- <div className="text-xs text-gray-500 ml-2">{helpText}</div>
89
- )}
90
- </div>
91
  );
92
 
93
  default:
 
1
  'use client';
2
 
3
  import { ParameterConfig } from '@/types/questionConfig';
4
+ import { SelectInput, RangeInput, TextInput, CheckboxInput } from './inputs';
5
 
6
  interface ParameterInputProps {
7
  config: ParameterConfig;
8
  value: any;
9
  onChange: (value: any) => void;
10
+ className?: string;
11
+ disabled?: boolean;
12
  }
13
 
14
+ export default function ParameterInput({
15
+ config,
16
+ value,
17
+ onChange,
18
+ className = '',
19
+ disabled = false
20
+ }: ParameterInputProps) {
21
+ const { type, label, options, min, max, step, placeholder, helpText, key } = config;
22
 
23
  switch (type) {
24
  case 'select':
25
  return (
26
+ <SelectInput
27
+ label={label}
28
+ value={value}
29
+ defaultValue={config.defaultValue}
30
+ options={options || []}
31
+ onChange={onChange}
32
+ className={className}
33
+ disabled={disabled}
34
+ placeholder={placeholder}
35
+ helpText={helpText}
36
+ />
 
 
 
 
 
 
37
  );
38
 
39
  case 'range':
40
  return (
41
+ <RangeInput
42
+ label={label}
43
+ value={value}
44
+ defaultValue={config.defaultValue}
45
+ min={min || 1}
46
+ max={max || 10}
47
+ step={step}
48
+ onChange={onChange}
49
+ className={className}
50
+ disabled={disabled}
51
+ valueUnit={helpText}
52
+ showValue={true}
53
+ />
 
 
54
  );
55
 
56
  case 'input':
57
  return (
58
+ <TextInput
59
+ label={label}
60
+ value={value}
61
+ defaultValue={config.defaultValue}
62
+ onChange={onChange}
63
+ className={className}
64
+ disabled={disabled}
65
+ placeholder={placeholder}
66
+ helpText={helpText}
67
+ />
 
 
 
68
  );
69
 
70
  case 'checkbox':
71
  return (
72
+ <CheckboxInput
73
+ label={label}
74
+ value={value}
75
+ defaultValue={config.defaultValue}
76
+ onChange={onChange}
77
+ className={className}
78
+ disabled={disabled}
79
+ helpText={helpText}
80
+ id={key}
81
+ />
 
 
 
82
  );
83
 
84
  default:
src/components/question-creation-card/ParameterInput.tsx ADDED
@@ -0,0 +1,87 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use client';
2
+
3
+ import { ParameterConfig } from '@/types/questionConfig';
4
+ import { SelectInput, RangeInput, TextInput, CheckboxInput } from './inputs';
5
+
6
+ interface ParameterInputProps {
7
+ config: ParameterConfig;
8
+ value: any;
9
+ onChange: (value: any) => void;
10
+ className?: string;
11
+ disabled?: boolean;
12
+ }
13
+
14
+ export default function ParameterInput({
15
+ config,
16
+ value,
17
+ onChange,
18
+ className = '',
19
+ disabled = false
20
+ }: ParameterInputProps) {
21
+ const { type, label, options, min, max, step, placeholder, helpText, key } = config;
22
+
23
+ switch (type) {
24
+ case 'select':
25
+ return (
26
+ <SelectInput
27
+ label={label}
28
+ value={value}
29
+ defaultValue={config.defaultValue}
30
+ options={options || []}
31
+ onChange={onChange}
32
+ className={className}
33
+ disabled={disabled}
34
+ placeholder={placeholder}
35
+ helpText={helpText}
36
+ />
37
+ );
38
+
39
+ case 'range':
40
+ return (
41
+ <RangeInput
42
+ label={label}
43
+ value={value}
44
+ defaultValue={config.defaultValue}
45
+ min={min || 1}
46
+ max={max || 10}
47
+ step={step}
48
+ onChange={onChange}
49
+ className={className}
50
+ disabled={disabled}
51
+ valueUnit={helpText}
52
+ showValue={true}
53
+ />
54
+ );
55
+
56
+ case 'input':
57
+ return (
58
+ <TextInput
59
+ label={label}
60
+ value={value}
61
+ defaultValue={config.defaultValue}
62
+ onChange={onChange}
63
+ className={className}
64
+ disabled={disabled}
65
+ placeholder={placeholder}
66
+ helpText={helpText}
67
+ />
68
+ );
69
+
70
+ case 'checkbox':
71
+ return (
72
+ <CheckboxInput
73
+ label={label}
74
+ value={value}
75
+ defaultValue={config.defaultValue}
76
+ onChange={onChange}
77
+ className={className}
78
+ disabled={disabled}
79
+ helpText={helpText}
80
+ id={key}
81
+ />
82
+ );
83
+
84
+ default:
85
+ return null;
86
+ }
87
+ }
src/components/{QuestionParameterForm.tsx → question-creation-card/QuestionParameterForm.tsx} RENAMED
@@ -1,7 +1,7 @@
1
  'use client';
2
 
3
  import { QuestionType, QuestionParameters } from '@/types/quiz';
4
- import { questionTypeConfigs, defaultQuestionTypeConfig } from '@/types/questionConfig';
5
  import ParameterInput from './ParameterInput';
6
 
7
  interface QuestionParameterFormProps {
 
1
  'use client';
2
 
3
  import { QuestionType, QuestionParameters } from '@/types/quiz';
4
+ import { questionTypeConfigs, defaultQuestionTypeConfig } from '@/config/questionTypes';
5
  import ParameterInput from './ParameterInput';
6
 
7
  interface QuestionParameterFormProps {
src/components/question-creation-card/index.ts ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ // Question Creation Card Components
2
+ export { default as QuestionParameterForm } from './QuestionParameterForm';
3
+ export { default as ParameterInput } from './ParameterInput';
4
+
5
+ // Re-export input components for convenience
6
+ export * from './inputs';
src/components/question-creation-card/inputs/CheckboxInput.tsx ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use client';
2
+
3
+ export interface CheckboxInputProps {
4
+ label: string;
5
+ value?: boolean;
6
+ defaultValue?: boolean;
7
+ onChange: (value: boolean) => void;
8
+ className?: string;
9
+ disabled?: boolean;
10
+ helpText?: string;
11
+ required?: boolean;
12
+ id?: string;
13
+ }
14
+
15
+ export default function CheckboxInput({
16
+ label,
17
+ value,
18
+ defaultValue = false,
19
+ onChange,
20
+ className = '',
21
+ disabled = false,
22
+ helpText,
23
+ required = false,
24
+ id,
25
+ }: CheckboxInputProps) {
26
+ const currentValue = value ?? defaultValue;
27
+ const inputId = id || `checkbox-${Math.random().toString(36).substr(2, 9)}`;
28
+
29
+ return (
30
+ <div className={`flex items-start ${className}`}>
31
+ <div className="flex items-center h-5">
32
+ <input
33
+ type="checkbox"
34
+ id={inputId}
35
+ checked={currentValue}
36
+ onChange={(e) => onChange(e.target.checked)}
37
+ disabled={disabled}
38
+ required={required}
39
+ className="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600 disabled:opacity-50 disabled:cursor-not-allowed"
40
+ />
41
+ </div>
42
+ <div className="ml-3">
43
+ <label htmlFor={inputId} className="text-sm font-medium text-gray-700 dark:text-gray-300">
44
+ {label}
45
+ {required && <span className="text-red-500 ml-1">*</span>}
46
+ </label>
47
+ {helpText && (
48
+ <div className="text-xs text-gray-500 dark:text-gray-400 mt-1">
49
+ {helpText}
50
+ </div>
51
+ )}
52
+ </div>
53
+ </div>
54
+ );
55
+ }
src/components/question-creation-card/inputs/RangeInput.tsx ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use client';
2
+
3
+ export interface RangeInputProps {
4
+ label: string;
5
+ value?: number;
6
+ defaultValue?: number;
7
+ min: number;
8
+ max: number;
9
+ step?: number;
10
+ onChange: (value: number) => void;
11
+ className?: string;
12
+ disabled?: boolean;
13
+ showValue?: boolean;
14
+ valueUnit?: string;
15
+ helpText?: string;
16
+ }
17
+
18
+ export default function RangeInput({
19
+ label,
20
+ value,
21
+ defaultValue = 1,
22
+ min,
23
+ max,
24
+ step = 1,
25
+ onChange,
26
+ className = '',
27
+ disabled = false,
28
+ showValue = true,
29
+ valueUnit = '',
30
+ helpText,
31
+ }: RangeInputProps) {
32
+ const currentValue = value ?? defaultValue;
33
+
34
+ return (
35
+ <div className={className}>
36
+ <label className="block text-sm font-medium mb-2 text-gray-700 dark:text-gray-300">
37
+ {label}
38
+ </label>
39
+ <input
40
+ type="range"
41
+ min={min}
42
+ max={max}
43
+ step={step}
44
+ value={currentValue}
45
+ onChange={(e) => onChange(parseInt(e.target.value))}
46
+ disabled={disabled}
47
+ className="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer dark:bg-gray-700 disabled:opacity-50 disabled:cursor-not-allowed"
48
+ />
49
+ {showValue && (
50
+ <div className="text-center text-sm text-gray-600 dark:text-gray-400 mt-1">
51
+ {currentValue} {valueUnit} {helpText}
52
+ </div>
53
+ )}
54
+ {!showValue && helpText && (
55
+ <div className="text-xs text-gray-500 dark:text-gray-400 mt-1">
56
+ {helpText}
57
+ </div>
58
+ )}
59
+ </div>
60
+ );
61
+ }
src/components/question-creation-card/inputs/SelectInput.tsx ADDED
@@ -0,0 +1,68 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use client';
2
+
3
+ export interface SelectOption {
4
+ value: string | number;
5
+ label: string;
6
+ }
7
+
8
+ export interface SelectInputProps {
9
+ label: string;
10
+ value?: string | number;
11
+ defaultValue?: string | number;
12
+ options: SelectOption[];
13
+ onChange: (value: string | number) => void;
14
+ className?: string;
15
+ disabled?: boolean;
16
+ placeholder?: string;
17
+ helpText?: string;
18
+ }
19
+
20
+ export default function SelectInput({
21
+ label,
22
+ value,
23
+ defaultValue,
24
+ options,
25
+ onChange,
26
+ className = '',
27
+ disabled = false,
28
+ placeholder,
29
+ helpText,
30
+ }: SelectInputProps) {
31
+ const currentValue = value ?? defaultValue;
32
+ const baseClasses = "w-full p-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed";
33
+
34
+ return (
35
+ <div className={className}>
36
+ <label className="block text-sm font-medium mb-2 text-gray-700 dark:text-gray-300">
37
+ {label}
38
+ </label>
39
+ <select
40
+ value={currentValue?.toString() || ''}
41
+ onChange={(e) => {
42
+ const selectedOption = options.find(opt => opt.value.toString() === e.target.value);
43
+ if (selectedOption) {
44
+ onChange(selectedOption.value);
45
+ }
46
+ }}
47
+ disabled={disabled}
48
+ className={`${baseClasses} bg-white dark:bg-gray-700 text-gray-900 dark:text-white border-gray-300 dark:border-gray-600`}
49
+ >
50
+ {placeholder && (
51
+ <option value="" disabled>
52
+ {placeholder}
53
+ </option>
54
+ )}
55
+ {options.map((option) => (
56
+ <option key={option.value.toString()} value={option.value.toString()}>
57
+ {option.label}
58
+ </option>
59
+ ))}
60
+ </select>
61
+ {helpText && (
62
+ <div className="text-xs text-gray-500 dark:text-gray-400 mt-1">
63
+ {helpText}
64
+ </div>
65
+ )}
66
+ </div>
67
+ );
68
+ }
src/components/question-creation-card/inputs/TextInput.tsx ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use client';
2
+
3
+ export interface TextInputProps {
4
+ label: string;
5
+ value?: string;
6
+ defaultValue?: string;
7
+ onChange: (value: string) => void;
8
+ type?: 'text' | 'email' | 'password' | 'url' | 'tel';
9
+ className?: string;
10
+ disabled?: boolean;
11
+ placeholder?: string;
12
+ helpText?: string;
13
+ required?: boolean;
14
+ maxLength?: number;
15
+ minLength?: number;
16
+ pattern?: string;
17
+ }
18
+
19
+ export default function TextInput({
20
+ label,
21
+ value,
22
+ defaultValue = '',
23
+ onChange,
24
+ type = 'text',
25
+ className = '',
26
+ disabled = false,
27
+ placeholder,
28
+ helpText,
29
+ required = false,
30
+ maxLength,
31
+ minLength,
32
+ pattern,
33
+ }: TextInputProps) {
34
+ const currentValue = value ?? defaultValue;
35
+ const baseClasses = "w-full p-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed";
36
+
37
+ return (
38
+ <div className={className}>
39
+ <label className="block text-sm font-medium mb-2 text-gray-700 dark:text-gray-300">
40
+ {label}
41
+ {required && <span className="text-red-500 ml-1">*</span>}
42
+ </label>
43
+ <input
44
+ type={type}
45
+ value={currentValue}
46
+ placeholder={placeholder}
47
+ onChange={(e) => onChange(e.target.value)}
48
+ disabled={disabled}
49
+ required={required}
50
+ maxLength={maxLength}
51
+ minLength={minLength}
52
+ pattern={pattern}
53
+ className={`${baseClasses} bg-white dark:bg-gray-700 text-gray-900 dark:text-white border-gray-300 dark:border-gray-600 placeholder-gray-500 dark:placeholder-gray-400`}
54
+ />
55
+ {helpText && (
56
+ <div className="text-xs text-gray-500 dark:text-gray-400 mt-1">
57
+ {helpText}
58
+ </div>
59
+ )}
60
+ </div>
61
+ );
62
+ }
src/components/question-creation-card/inputs/index.ts ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Independent input components
2
+ export { default as SelectInput } from './SelectInput';
3
+ export type { SelectInputProps, SelectOption } from './SelectInput';
4
+
5
+ export { default as RangeInput } from './RangeInput';
6
+ export type { RangeInputProps } from './RangeInput';
7
+
8
+ export { default as TextInput } from './TextInput';
9
+ export type { TextInputProps } from './TextInput';
10
+
11
+ export { default as CheckboxInput } from './CheckboxInput';
12
+ export type { CheckboxInputProps } from './CheckboxInput';
src/{types/questionConfig.ts → config/questionTypes.ts} RENAMED
@@ -1,27 +1,4 @@
1
- export type ParameterType = 'select' | 'range' | 'input' | 'checkbox';
2
-
3
- export interface ParameterOption {
4
- value: string | number;
5
- label: string;
6
- }
7
-
8
- export interface ParameterConfig {
9
- key: string;
10
- label: string;
11
- type: ParameterType;
12
- defaultValue?: any;
13
- options?: ParameterOption[];
14
- min?: number;
15
- max?: number;
16
- step?: number;
17
- placeholder?: string;
18
- helpText?: string;
19
- }
20
-
21
- export interface QuestionTypeConfig {
22
- id: string;
23
- parameters: ParameterConfig[];
24
- }
25
 
26
  // Configuration for each question type
27
  export const questionTypeConfigs: Record<string, QuestionTypeConfig> = {
 
1
+ import { QuestionTypeConfig } from '@/types/questionConfig';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
 
3
  // Configuration for each question type
4
  export const questionTypeConfigs: Record<string, QuestionTypeConfig> = {