You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

169 lines
7.3 KiB

  1. import { useEffect, useState } from 'react';
  2. import ReactQuill from 'react-quill';
  3. import 'react-quill/dist/quill.snow.css';
  4. import CodeMirror from '@uiw/react-codemirror';
  5. import { html } from '@codemirror/lang-html';
  6. import { javascript } from '@codemirror/lang-javascript';
  7. import { php } from '@codemirror/lang-php';
  8. import 'codemirror/lib/codemirror.css';
  9. import 'codemirror/theme/material.css';
  10. import PrimaryButton from '@/Components/PrimaryButton';
  11. import { Code, Download, Minus, MinusCircle, Plus } from 'react-feather';
  12. import FormGroup from '@/Components/FormGroup';
  13. import InputLabel from '@/Components/InputLabel';
  14. import TextInput from '@/Components/TextInput';
  15. import AddFrameworkModal from '../AppFramework/AddModal';
  16. import axios from 'axios';
  17. interface InstructionsProps {
  18. instruction_id: number;
  19. onDelete: (instruction_id: number) => void;
  20. onUpdate: (instruction_id: number, steps: any[], frameworkID: string) => void;
  21. }
  22. interface Framework {
  23. id: number;
  24. framework_name: string;
  25. version: string;
  26. }
  27. export default function Instructions({ instruction_id, onDelete, onUpdate }: InstructionsProps) {
  28. const [frameworkID, setFrameworkID] = useState('');
  29. const [frameworks, setFrameworks] = useState<Framework[]>([]);
  30. const fetchFrameworks = async () => {
  31. try {
  32. const response = await axios.get<Framework[]>(route('frameworks.index'));
  33. setFrameworks(response.data);
  34. } catch (error) {
  35. console.error('Failed to fetch frameworks', error);
  36. }
  37. };
  38. useEffect(() => {
  39. fetchFrameworks();
  40. }, []);
  41. const [isAddFrameworkModalOpen, setIsAddFrameworkModalOpen] = useState(false);
  42. const handleChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
  43. const selectedValue = e.target.value;
  44. setFrameworkID(selectedValue);
  45. if (selectedValue === "0") {
  46. setIsAddFrameworkModalOpen(true);
  47. }
  48. }
  49. const [steps, setSteps] = useState<Array<{ title: string, content: string, code: string }>>([]);
  50. const handleAddStep = () => {
  51. setSteps([...steps, { title: '', content: '', code: '' }]);
  52. };
  53. const handleRemoveStep = (step_id: number) => {
  54. setSteps(prevSteps => prevSteps.filter((_, index) => index !== step_id));
  55. };
  56. const handleTitleChange = (step_id: number, title: string) => {
  57. const newSteps = [...steps];
  58. newSteps[step_id].title = title;
  59. setSteps(newSteps);
  60. onUpdate(instruction_id, newSteps, frameworkID);
  61. };
  62. const handleContentChange = (step_id: number, content: string) => {
  63. const newSteps = [...steps];
  64. newSteps[step_id].content = content;
  65. setSteps(newSteps);
  66. onUpdate(instruction_id, newSteps, frameworkID);
  67. };
  68. const handleCodeChange = (step_id: number, code: string) => {
  69. const newSteps = [...steps];
  70. newSteps[step_id].code = code;
  71. setSteps(newSteps);
  72. onUpdate(instruction_id, newSteps, frameworkID);
  73. };
  74. return (
  75. <div className="h-full w-full bg-neutral-10 shadow-md my-4 p-6 rounded-lg flex flex-col items-start">
  76. <div className='flex w-full justify-between items-center'>
  77. <h1 className="font-bold text-lg text-secondary-main flex items-center my-2">Instructions Editor</h1>
  78. <button className="btn btn-link text-error-main p-0 no-underline" onClick={() => onDelete(instruction_id)}>
  79. <Minus size={20} />
  80. Remove Instruction
  81. </button>
  82. </div>
  83. <div className='w-full'>
  84. <FormGroup>
  85. <InputLabel htmlFor="frameworkID"><Code className='stroke-neutral-10' /></InputLabel>
  86. <label htmlFor="frameworkID" className="ml-2 w-1/4">Application Framework:</label>
  87. <select name="frameworkID" id="frameworkID" value={frameworkID} onChange={handleChange}
  88. className='px-2 py-0 flex-grow w-full h-[30px] bg-primary-background border border-primary-hover focus:outline-none focus:bg-neutral-10 focus:border focus:border-primary-hover'>
  89. <option value="" selected disabled></option>
  90. {frameworks.map(framework => (
  91. <option key={framework.id} value={framework.id}>
  92. {framework.framework_name}
  93. </option>
  94. ))}
  95. <option value="0" className="text-secondary-main font-semibold">Add new framework</option>
  96. </select>
  97. </FormGroup>
  98. </div>
  99. <div className='w-full my-4'>
  100. {steps.map((step, step_id) => (
  101. <div key={step_id}>
  102. <div className='flex justify-between items-center'>
  103. <div className='flex w-full items-center'>
  104. <h3 className='font-semibold text-lg w-1/7'>Step {step_id + 1}</h3>
  105. <TextInput
  106. id='stepTitle'
  107. name='stepTitle'
  108. value={step.title}
  109. placeholder='Step Title'
  110. autoComplete='off'
  111. required
  112. onChange={(e) => handleTitleChange(step_id, e.target.value)}
  113. className='w-1/4 mx-4'
  114. />
  115. </div>
  116. <button type="button" className="btn btn-link text-error-main p-0 no-underline" onClick={() => handleRemoveStep(step_id)}>
  117. <Minus size={20} />
  118. Remove Step
  119. </button>
  120. </div>
  121. <p>Textual Instruction:</p>
  122. <ReactQuill
  123. value={step.content}
  124. onChange={(content) => handleContentChange(step_id, content)}
  125. className="h-1/2"
  126. modules={{
  127. toolbar: [
  128. ['bold', 'italic', 'underline'],
  129. [{'list': 'ordered'}, {'list': 'bullet'}],
  130. ['link', 'image', 'video']
  131. ]
  132. }}
  133. />
  134. <p className='mt-2'>Code Snippet:</p>
  135. <CodeMirror
  136. value={step.code}
  137. extensions={[php(), javascript(), html()]}
  138. onChange={(value) => handleCodeChange(step_id, value)}
  139. />
  140. <div className="divider divider-neutral-20"></div>
  141. </div>
  142. ))}
  143. </div>
  144. <PrimaryButton type="button" onClick={handleAddStep} className='w-full'>
  145. <Plus className='stroke-neutral-10' />
  146. Add Steps
  147. </PrimaryButton>
  148. <AddFrameworkModal show={isAddFrameworkModalOpen} onClose={() => setIsAddFrameworkModalOpen(false)} onFrameworkAdded={fetchFrameworks} />
  149. </div>
  150. );
  151. };