@@ -19,96 +19,83 @@ const FormularioReports = () => {
1919 const [ fileList , setFileList ] = useState ( [ ] ) ;
2020
2121 // Tipos predefinidos de incidentes
22- const tipos = [
23- "Infraestructura y mantenimiento" ,
24- "Seguridad" ,
25- "Salud y bienestar" ,
26- "Convivencia" ,
27- "Tecnología y conectividad" ,
28- "Recursos académicos" ,
29- "Gestión administrativa" ,
30- "Medio ambiente" ,
31- "Comunicación y señalización" ,
32- "Otros"
33- ] ;
34-
22+ const tipos = [
23+ "Infraestructura y mantenimiento" ,
24+ "Seguridad" ,
25+ "Salud y bienestar" ,
26+ "Convivencia" ,
27+ "Tecnología y conectividad" ,
28+ "Recursos académicos" ,
29+ "Gestión administrativa" ,
30+ "Medio ambiente" ,
31+ "Comunicación y señalización" ,
32+ "Otros" ,
33+ ] ;
3534
3635 // Enviar formulario
37- // Enviar formulario
38- const onFinish = async ( values ) => {
39- try {
40- setSubmitting ( true ) ;
41-
42- // Crear FormData para enviar texto y archivo
43- const formData = new FormData ( ) ;
44- formData . append ( "tipo_incidente" , values . tipo_incidente . trim ( ) ) ;
45- formData . append ( "descripcion" , values . descripcion . trim ( ) ) ;
46-
47- // Ubicación requerida
48- formData . append ( "ubicacion" , values . ubicacion . trim ( ) ) ;
49-
50- // Fecha y hora opcional (usa la actual si no se seleccionó)
51- // Fecha seleccionada + hora actual del sistema
52- const fechaHora = values . fecha_hora
53- ? dayjs ( values . fecha_hora )
54- . hour ( dayjs ( ) . hour ( ) ) // hora actual
55- . minute ( dayjs ( ) . minute ( ) ) // minuto actual
56- . second ( dayjs ( ) . second ( ) ) // segundo actual
57- . format ( "YYYY-MM-DD HH:mm:ss" )
58- : dayjs ( ) . format ( "YYYY-MM-DD HH:mm:ss" ) ;
59- formData . append ( "fecha_hora" , fechaHora ) ;
60-
61-
62- // Archivo opcional con compresión
63- if ( fileList . length > 0 && fileList [ 0 ] . originFileObj ) {
64- const compressedFile = await reduceImage ( fileList [ 0 ] . originFileObj , 1024 , 1024 , 0.7 ) ;
65- formData . append ( "foto" , compressedFile ) ;
66- }
67-
68- // Enviar la solicitud
69- const res = await fetch ( `${ backend } /reports/add` , {
70- method : "POST" ,
71- body : formData ,
72- } ) ;
73-
74- const data = await res . json ( ) . catch ( ( ) => ( {
75- message : "Error inesperado en la respuesta del servidor." ,
76- } ) ) ;
77-
78- if ( res . ok ) {
79- message . success ( "✅ ¡Reporte enviado correctamente!" ) ;
80- form . resetFields ( ) ;
81- setFileList ( [ ] ) ;
82- setShowAnimation ( true ) ;
36+ const onFinish = async ( values ) => {
37+ try {
38+ setSubmitting ( true ) ;
39+
40+ const formData = new FormData ( ) ;
41+ formData . append ( "tipo_incidente" , values . tipo_incidente . trim ( ) ) ;
42+ formData . append ( "descripcion" , values . descripcion . trim ( ) ) ;
43+ formData . append ( "ubicacion" , values . ubicacion . trim ( ) ) ;
44+
45+ // Fecha y hora (combina fecha seleccionada + hora actual)
46+ const fechaHora = values . fecha_hora
47+ ? dayjs ( values . fecha_hora )
48+ . hour ( dayjs ( ) . hour ( ) )
49+ . minute ( dayjs ( ) . minute ( ) )
50+ . second ( dayjs ( ) . second ( ) )
51+ . format ( "YYYY-MM-DD HH:mm:ss" )
52+ : dayjs ( ) . format ( "YYYY-MM-DD HH:mm:ss" ) ;
53+ formData . append ( "fecha_hora" , fechaHora ) ;
54+
55+ // Archivo opcional con compresión
56+ if ( fileList . length > 0 && fileList [ 0 ] . originFileObj ) {
57+ const compressedFile = await reduceImage ( fileList [ 0 ] . originFileObj , 1024 , 1024 , 0.7 ) ;
58+ formData . append ( "foto" , compressedFile ) ;
59+ }
8360
84- // Esperar un poco para mostrar la animación antes de redirigir
85- setTimeout ( ( ) => {
86- setShowAnimation ( false ) ;
87- navigate ( "/reports" ) ; // redirige a la lista de reportes
88- } , 1500 ) ;
89- } else {
90- console . error ( "Error del servidor:" , data ) ;
91- Swal . fire ( "Error" , data . message || "Ocurrió un error al enviar el reporte." , "error" ) ;
61+ // Enviar solicitud
62+ const res = await fetch ( `${ backend } /reports/add` , {
63+ method : "POST" ,
64+ body : formData ,
65+ } ) ;
66+
67+ const data = await res . json ( ) . catch ( ( ) => ( {
68+ message : "Error inesperado en la respuesta del servidor." ,
69+ } ) ) ;
70+
71+ if ( res . ok ) {
72+ message . success ( "✅ ¡Reporte enviado correctamente!" ) ;
73+ form . resetFields ( ) ;
74+ setFileList ( [ ] ) ;
75+ setShowAnimation ( true ) ;
76+
77+ setTimeout ( ( ) => {
78+ setShowAnimation ( false ) ;
79+ navigate ( "/reports" ) ;
80+ } , 1500 ) ;
81+ } else {
82+ console . error ( "Error del servidor:" , data ) ;
83+ Swal . fire ( "Error" , data . message || "Ocurrió un error al enviar el reporte." , "error" ) ;
84+ }
85+ } catch ( err ) {
86+ console . error ( "Error en el envío:" , err ) ;
87+ Swal . fire ( "Error" , "No se pudo enviar el reporte. Verifica tu conexión y formato de archivo." , "error" ) ;
88+ } finally {
89+ setSubmitting ( false ) ;
9290 }
93- } catch ( err ) {
94- console . error ( "Error en el envío:" , err ) ;
95- Swal . fire ( "Error" , "No se pudo enviar el reporte. Verifica tu conexión." , "error" ) ;
96- } finally {
97- setSubmitting ( false ) ;
98- }
99- } ;
100-
101-
91+ } ;
10292
10393 return (
10494 < div style = { { width : "100%" , position : "relative" } } >
10595 { /* Encabezado */ }
10696 < div style = { { display : "flex" , flexDirection : "column" , marginBottom : 20 } } >
107- < div
108- style = { { display : "flex" , alignItems : "center" , cursor : "pointer" } }
109-
110- >
111- < ArrowLeftOutlined style = { { fontSize : 24 , marginRight : 12 , color : "#000" } } onClick = { ( ) => navigate ( - 1 ) } />
97+ < div style = { { display : "flex" , alignItems : "center" , cursor : "pointer" } } >
98+ < ArrowLeftOutlined style = { { fontSize : 24 , marginRight : 12 , color : "#000" } } onClick = { ( ) => navigate ( - 1 ) } />
11299 < Title level = { 3 } style = { { margin : 0 } } >
113100 Reportar Incidencia
114101 </ Title >
@@ -121,7 +108,7 @@ formData.append("fecha_hora", fechaHora);
121108
122109 { /* Formulario */ }
123110 < Form layout = "vertical" form = { form } onFinish = { onFinish } >
124- { /* Tipo de incidente */ }
111+ { /* Tipo */ }
125112 < Form . Item
126113 label = "Tipo de incidencia"
127114 name = "tipo_incidente"
@@ -131,9 +118,7 @@ formData.append("fecha_hora", fechaHora);
131118 placeholder = "Selecciona un tipo"
132119 showSearch
133120 optionFilterProp = "children"
134- filterOption = { ( input , option ) =>
135- option . children . toLowerCase ( ) . includes ( input . toLowerCase ( ) )
136- }
121+ filterOption = { ( input , option ) => option . children . toLowerCase ( ) . includes ( input . toLowerCase ( ) ) }
137122 >
138123 { tipos . map ( ( t ) => (
139124 < Option key = { t } value = { t } >
@@ -144,91 +129,90 @@ formData.append("fecha_hora", fechaHora);
144129 </ Form . Item >
145130
146131 { /* Descripción */ }
147- < Form . Item
148- label = "Descripción"
149- name = "descripcion"
150- rules = { [ { required : true , message : "Describe la incidencia" } ] }
151- >
152- < TextArea
153- rows = { 4 }
154- placeholder = "Describe detalladamente lo ocurrido..."
155- maxLength = { 3000 }
156- style = { { resize : "none" } }
157- />
132+ < Form . Item label = "Descripción" name = "descripcion" rules = { [ { required : true , message : "Describe la incidencia" } ] } >
133+ < TextArea rows = { 4 } placeholder = "Describe detalladamente lo ocurrido..." maxLength = { 3000 } style = { { resize : "none" } } />
158134 </ Form . Item >
159135
160136 { /* Ubicación */ }
161- < Form . Item label = "Ubicación" name = "ubicacion" rules = { [ { required : true , message : "Indica la ubicación en el campus" } ] } >
137+ < Form . Item
138+ label = "Ubicación"
139+ name = "ubicacion"
140+ rules = { [ { required : true , message : "Indica la ubicación en el campus" } ] }
141+ >
162142 < Input placeholder = "Ej. Pasillo principal, baño del segundo piso, etc." />
163143 </ Form . Item >
164144
165- { /* Fecha y hora (opcional) */ }
166- { /* Fecha y hora (required) */ }
167- < Form . Item
168- label = "Fecha del incidente"
169- name = "fecha_hora"
170- rules = { [ { required : true , message : "Selecciona la fecha del incidente" } ] }
171- >
172- < DatePicker
173- style = { { width : "100%" } }
174- placeholder = "Selecciona la fecha del incidente"
175- />
176- </ Form . Item >
177-
178-
145+ { /* Fecha */ }
146+ < Form . Item
147+ label = "Fecha del incidente"
148+ name = "fecha_hora"
149+ rules = { [ { required : true , message : "Selecciona la fecha del incidente" } ] }
150+ >
151+ < DatePicker style = { { width : "100%" } } placeholder = "Selecciona la fecha del incidente" />
152+ </ Form . Item >
179153
180154 { /* Foto */ }
181- < Form . Item label = "Evidencia fotográfica (opcional)" >
182- { /* Input oculto que abre la cámara */ }
183- < input
184- type = "file"
185- accept = "image/*"
186- capture = "environment"
187- style = { { display : "none" } }
188- id = "cameraInput"
189- onChange = { ( e ) => {
190- if ( e . target . files . length > 0 ) {
191- const file = e . target . files [ 0 ] ;
192- if ( ! file . type . startsWith ( "image/" ) ) {
193- message . error ( "Solo se permiten archivos de imagen." ) ;
194- return ;
195- }
196- setFileList ( [ { originFileObj : file , uid : '-1' , name : file . name } ] ) ;
197- }
198- } }
199- />
200-
201-
202- < Upload
203- beforeUpload = { ( file ) => {
204- const isImage = file . type . startsWith ( "image/" ) ;
205- if ( ! isImage ) {
206- message . error ( "Solo se permiten archivos de imagen." ) ;
207- }
208- return isImage ? true : Upload . LIST_IGNORE ; // Ignora archivos no válidos
209- } }
210- fileList = { fileList }
211- onChange = { ( { fileList } ) => setFileList ( fileList . slice ( - 1 ) ) } // Solo 1 foto
212- listType = "picture"
213- accept = "image/*"
214- maxCount = { 1 } // Solo 1 imagen
215- style = { { width : "100%" } }
216- >
217- < Button style = { { width : "100%" } } icon = { < UploadOutlined /> } > Subir desde galería</ Button >
218- </ Upload >
219-
220-
221- < Button
222- icon = { < CameraOutlined /> }
223- type = "dashed"
224- style = { { width : "100%" , marginTop : 8 } }
225- onClick = { ( ) => document . getElementById ( "cameraInput" ) . click ( ) }
226- >
227- Tomar foto con cámara
228- </ Button >
229- </ Form . Item >
230-
155+ < Form . Item label = "Evidencia fotográfica (opcional)" >
156+ { /* Input cámara */ }
157+ < input
158+ type = "file"
159+ accept = "image/*"
160+ capture = "environment"
161+ style = { { display : "none" } }
162+ id = "cameraInput"
163+ onChange = { ( e ) => {
164+ if ( e . target . files . length > 0 ) {
165+ const file = e . target . files [ 0 ] ;
166+ if ( ! file . type . startsWith ( "image/" ) ) {
167+ Swal . fire ( {
168+ icon : "error" ,
169+ title : "Archivo no permitido" ,
170+ text : "Solo se pueden subir archivos de imagen (JPG, PNG, GIF, WEBP)." ,
171+ confirmButtonColor : "#d33" ,
172+ } ) ;
173+ e . target . value = "" ;
174+ return ;
175+ }
176+ setFileList ( [ { originFileObj : file , uid : "-1" , name : file . name } ] ) ;
177+ }
178+ } }
179+ />
231180
181+ { /* Upload desde galería */ }
182+ < Upload
183+ beforeUpload = { ( file ) => {
184+ const isImage = file . type . startsWith ( "image/" ) ;
185+ if ( ! isImage ) {
186+ Swal . fire ( {
187+ icon : "error" ,
188+ title : "Archivo no permitido" ,
189+ text : "Solo se pueden subir archivos de imagen (JPG, PNG, GIF, WEBP)." ,
190+ confirmButtonColor : "#d33" ,
191+ } ) ;
192+ }
193+ return isImage ? true : Upload . LIST_IGNORE ;
194+ } }
195+ fileList = { fileList }
196+ onChange = { ( { fileList } ) => setFileList ( fileList . slice ( - 1 ) ) }
197+ listType = "picture"
198+ accept = "image/*"
199+ maxCount = { 1 }
200+ style = { { width : "100%" } }
201+ >
202+ < Button style = { { width : "100%" } } icon = { < UploadOutlined /> } >
203+ Subir desde galería
204+ </ Button >
205+ </ Upload >
206+
207+ < Button
208+ icon = { < CameraOutlined /> }
209+ type = "dashed"
210+ style = { { width : "100%" , marginTop : 8 } }
211+ onClick = { ( ) => document . getElementById ( "cameraInput" ) . click ( ) }
212+ >
213+ Tomar foto con cámara
214+ </ Button >
215+ </ Form . Item >
232216
233217 { /* Botón enviar */ }
234218 < Form . Item >
0 commit comments