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.

284 lines
13 KiB

  1. import FormGroup from "@/Components/FormGroup";
  2. import InputError from "@/Components/InputError";
  3. import InputLabel from "@/Components/InputLabel";
  4. import TextInput from "@/Components/TextInput";
  5. import Authenticated from "@/Layouts/AuthenticatedLayout";
  6. import { PageProps } from "@/types";
  7. import { Head, Link, useForm } from "@inertiajs/react";
  8. import { Book, Box, CheckCircle, FileText, Paperclip, Plus, PlusCircle, XCircle, XOctagon } from "react-feather";
  9. import 'codemirror/lib/codemirror.css';
  10. import 'codemirror/mode/javascript/javascript';
  11. import PrimaryButton from "@/Components/PrimaryButton";
  12. import Instructions from "./InstructionsForm";
  13. import Modal from "@/Components/Modal";
  14. import ModalButton from "@/Components/ModalButton";
  15. import { FormEventHandler, useEffect, useState } from "react";
  16. import AddCategoryModal from "../SubjectCategory/AddModal";
  17. import axios from "axios";
  18. import SuccessButton from "@/Components/SuccessButton";
  19. import ErrorButton from "@/Components/ErrorButton";
  20. import dayjs from "dayjs";
  21. interface Category {
  22. id: number;
  23. subject_title: string;
  24. description?: string;
  25. }
  26. export default function Create({ auth }: PageProps) {
  27. const thisUser = auth.user;
  28. const { data, setData, processing, errors } = useForm({
  29. pageTitle: '',
  30. category: '',
  31. introduction:'',
  32. });
  33. const [isAddCategoryModalOpen, setIsAddCategoryModalOpen] = useState(false);
  34. const handleChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
  35. const selectedValue = e.target.value;
  36. setData('category', selectedValue);
  37. if (selectedValue === "0") {
  38. setIsAddCategoryModalOpen(true);
  39. }
  40. }
  41. const [status, setStatus] = useState('');
  42. const [feedback, setFeedback] = useState('');
  43. const [isSuccessModalOpen, setIsSuccessModalOpen] = useState(false);
  44. const [isErrorModalOpen, setIsErrorModalOpen] = useState(false);
  45. const [categories, setCategories] = useState<Category[]>([]);
  46. const fetchCategories = async () => {
  47. try {
  48. const response = await axios.get<Category[]>(route('categories.index'));
  49. setCategories(response.data);
  50. } catch (e) {
  51. setStatus('Function Failure');
  52. setFeedback('Failed to fetch categories');
  53. setIsErrorModalOpen(true);
  54. }
  55. };
  56. useEffect(() => {
  57. if (thisUser.role_id !== 1) {
  58. setStatus('Warning');
  59. setFeedback('You are unauthorized to access this page');
  60. setIsErrorModalOpen(true);
  61. } else {
  62. fetchCategories();
  63. }
  64. }, [thisUser.role_id]);
  65. const handleCloseSuccessModal = () => {
  66. setIsSuccessModalOpen(false);
  67. window.location.href = '/dashboard';
  68. }
  69. const handleCloseErrorModal = () => {
  70. setIsErrorModalOpen(false);
  71. window.location.href = '/dashboard';
  72. };
  73. const [instructions, setInstructions] = useState<{ id: number; steps: any[]; frameworkID: string; }[]>([]);
  74. const [nextId, setNextId] = useState<number>(0);
  75. const handleAddInstruction = () => {
  76. setInstructions([...instructions, { id: nextId, steps: [], frameworkID: '' }]);
  77. setNextId(nextId + 1);
  78. };
  79. const handleRemoveInstruction = (id: number) => {
  80. setInstructions(prevInstructions =>
  81. prevInstructions.filter(instruction => instruction.id !== id)
  82. );
  83. };
  84. const handleUpdateInstruction = (id: number, steps: any[], frameworkID: string) => {
  85. setInstructions(prevInstructions =>
  86. prevInstructions.map(instruction =>
  87. instruction.id === id ? { ...instruction, steps, frameworkID } : instruction
  88. )
  89. );
  90. };
  91. const submit: FormEventHandler = async (e) => {
  92. e.preventDefault();
  93. if (data.category==="0" || data.category==="") {
  94. alert('Please choose a valid category.')
  95. return
  96. }
  97. try {
  98. const response = await axios.post(route('page.add'), data);
  99. const pageID = response.data.page.id;
  100. await Promise.all(instructions.map(async (instruction) => {
  101. if (instruction.frameworkID === "0" || instruction.frameworkID === "") {
  102. alert('Please choose a valid framework for each instruction.');
  103. return;
  104. }
  105. const timestamp = dayjs().format('YYYYMMDDHHmmss');
  106. const jsonString = JSON.stringify({ steps: instruction.steps });
  107. const blob = new Blob([jsonString], { type: 'application/json' });
  108. const file = new File([blob], `${timestamp}.json`, { type: 'application/json' });
  109. const formData = new FormData();
  110. formData.append('file', file);
  111. formData.append('frameworkID', instruction.frameworkID);
  112. formData.append('pageID', pageID.toString());
  113. await axios.post(route('instruction.add'), formData, {
  114. headers: {
  115. 'Content-Type': 'multipart/form-data',
  116. },
  117. });
  118. }));
  119. setStatus('Success');
  120. setFeedback(response.data.message);
  121. setIsSuccessModalOpen(true);
  122. } catch (error) {
  123. setStatus('Error')
  124. if (axios.isAxiosError(error)) {
  125. setFeedback(error.response?.data.message || 'An error occurred');
  126. } else {
  127. setFeedback('An unexpected error occurred');
  128. }
  129. setIsErrorModalOpen(true);
  130. }
  131. }
  132. const renderIfAdmin = () => {
  133. if (thisUser.role_id === 1) {
  134. return (
  135. <div>
  136. <div className="breadcrumbs text-sm">
  137. <ul>
  138. <li><Link href="/dashboard">Home</Link></li>
  139. <li><a>Repository Pages</a></li>
  140. <li>Add New Repository Page</li>
  141. </ul>
  142. </div>
  143. <h1 className="font-bold text-xl text-primary-main flex items-center my-2"><FileText className="stroke-primary-main mr-2" />Create Repository Page</h1>
  144. <div className="divider"></div>
  145. <form id="CreateRepositoryPageForm" onSubmit={submit}>
  146. <div>
  147. <FormGroup>
  148. <InputLabel htmlFor="pageTitle"><Box className='stroke-neutral-10' /></InputLabel>
  149. <label htmlFor="pageTitle" className="mx-2 font-semibold">Page Title:</label>
  150. </FormGroup>
  151. <TextInput
  152. id='pageTitle'
  153. name='pageTitle'
  154. value={data.pageTitle}
  155. autoComplete='off'
  156. required
  157. onChange={(e) => setData('pageTitle', e.target.value)}
  158. />
  159. <InputError message={errors.pageTitle} className="mt-2" />
  160. </div>
  161. <div>
  162. <FormGroup>
  163. <InputLabel htmlFor="category"><Book className='stroke-neutral-10' /></InputLabel>
  164. <label htmlFor="category" className="mx-2 font-semibold">Subject Category:</label>
  165. </FormGroup>
  166. <select name="category" id="category" value={data.category} onChange={handleChange}
  167. 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'>
  168. <option value="" selected disabled></option>
  169. {categories.map(category => (
  170. <option key={category.id} value={category.id}>
  171. {category.subject_title}
  172. </option>
  173. ))}
  174. <option value="0" className="text-secondary-main font-semibold">Add new framework</option>
  175. </select>
  176. </div>
  177. <div>
  178. <FormGroup>
  179. <InputLabel htmlFor="description"><Paperclip className='stroke-neutral-10' /></InputLabel>
  180. <label htmlFor="description" className="mx-2 font-semibold">Introduction:</label>
  181. </FormGroup>
  182. <textarea className='px-2 flex-grow w-full bg-primary-background border border-primary-hover focus:outline-none focus:bg-neutral-10 focus:border-2 focus:border-primary-hover '
  183. onChange={(e) => setData('introduction', e.target.value)} />
  184. </div>
  185. {instructions.map((instruction) => (
  186. <Instructions
  187. key={instruction.id}
  188. instruction_id={instruction.id}
  189. onDelete={handleRemoveInstruction}
  190. onUpdate={handleUpdateInstruction}
  191. />
  192. ))}
  193. <PrimaryButton type='button' disabled={processing} onClick={handleAddInstruction} className="w-full">
  194. <Plus className='stroke-neutral-10' />
  195. Add Instruction
  196. </PrimaryButton>
  197. <div className="divider"></div>
  198. <div className="flex">
  199. <ErrorButton className="w-1/2" onClick={() => window.location.href = '/dashboard'}>
  200. <XCircle className='stroke-neutral-10' />
  201. Cancel
  202. </ErrorButton>
  203. <SuccessButton type="submit" className="w-1/2">
  204. <PlusCircle className='stroke-neutral-10' />
  205. Save Repository Page
  206. </SuccessButton>
  207. </div>
  208. </form>
  209. </div>
  210. );
  211. }
  212. }
  213. return (
  214. <Authenticated user={thisUser}>
  215. <Head title="Repository Page" />
  216. <div id="CreateRepositoryPage" className="drawer lg:drawer-open">
  217. <input id="my-drawer-2" type="checkbox" className="drawer-toggle" />
  218. <div className="drawer-content flex flex-row justify-between p-6 bg-neutral-20">
  219. <div className="h-full w-full bg-neutral-10 shadow-md p-6 rounded-lg">
  220. {renderIfAdmin()}
  221. </div>
  222. </div>
  223. <div className="drawer-side">
  224. <label htmlFor="my-drawer-2" aria-label="close sidebar" className="drawer-overlay"></label>
  225. <ul className="menu p-4 w-56 h-full bg-primary-background text-base-content">
  226. {/* Sidebar content here */}
  227. <li><a>Sidebar Item 1</a></li>
  228. <li><a>Sidebar Item 2</a></li>
  229. </ul>
  230. </div>
  231. </div>
  232. <AddCategoryModal show={isAddCategoryModalOpen} onClose={() => setIsAddCategoryModalOpen(false)} onCategoryAdded={fetchCategories} />
  233. <Modal show={isSuccessModalOpen} onClose={() => setIsSuccessModalOpen(false)} maxWidth="lg" styling='success'>
  234. <div className="p-4 flex flex-col items-center">
  235. <CheckCircle className='stroke-success-main' size={80} />
  236. <h2 className="text-xl font-bold mt-2">{ status }</h2>
  237. <p className="mt-4">{ feedback }</p>
  238. <ModalButton onClick={handleCloseSuccessModal} className="bg-success-main text-white hover:bg-success-hover active:bg-success-pressed">
  239. Close
  240. </ModalButton>
  241. </div>
  242. </Modal>
  243. <Modal show={isErrorModalOpen} onClose={() => setIsErrorModalOpen(false)} maxWidth="lg" styling='error'>
  244. <div className="p-4 flex flex-col items-center">
  245. <XOctagon className='stroke-error-main' size={80} />
  246. <h2 className="text-xl font-bold mt-2">{ status }</h2>
  247. <p className="mt-4">{ feedback }</p>
  248. <ModalButton onClick={handleCloseErrorModal} className="bg-error-main text-white hover:bg-error-hover active:bg-error-pressed">
  249. Close
  250. </ModalButton>
  251. </div>
  252. </Modal>
  253. </Authenticated>
  254. );
  255. }