Skip to content

Commit 3705177

Browse files
creating forms for edit job
1 parent 24335f6 commit 3705177

6 files changed

Lines changed: 404 additions & 8 deletions

File tree

src/pages/editar-empleo.astro

Lines changed: 267 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,267 @@
1+
---
2+
import BaseLayout from "../layouts/baseLayout.astro";
3+
import CheckboxInput from "../components/forms/checkboxInput.astro";
4+
import RadioButtonInput from "../components/forms/radioButtonInput.astro";
5+
import UsernameInput from "../components/forms/usernameInput.astro";
6+
import TextInput from "../components/forms/textInput.astro";
7+
import Toast from "../components/toast/toast.astro";
8+
import "../styles/main.css";
9+
import categories from "../data/profileCategories.json";
10+
11+
let categoriesList: any[] = [];
12+
for (const key in categories) {
13+
(categories as any)[key].key = key;
14+
categoriesList.push((categories as any)[key]);
15+
}
16+
---
17+
18+
<BaseLayout title="Editar Empleo | CuCoders" description="Edita tu oferta de empleo en CuCoders.">
19+
<div class="lg:max-w-[58rem] mx-auto w-full md:mt-12">
20+
<form
21+
id="job-form"
22+
class="bg-white border md:mx-auto mb-5 p-8 lg:max-w-[58rem] border-gray-200 rounded-lg dark:bg-gray-800 dark:border-gray-700 w-full"
23+
>
24+
<div class="flex justify-between">
25+
<h1 class="text-xl font-bold dark:text-gray-100">Editar Empleo</h1>
26+
</div>
27+
28+
<hr class="my-4" />
29+
30+
<div>
31+
<div class="mb-6">
32+
<TextInput id="title" label="Título *" maxlength="100" required={true} />
33+
</div>
34+
35+
<div class="mb-6">
36+
<TextInput id="salary" label="Salario" maxlength="60" />
37+
</div>
38+
39+
<div class="mb-6">
40+
<label for="description" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Descripción *</label>
41+
<textarea
42+
id="description"
43+
required={true}
44+
rows="6"
45+
class="block p-2.5 w-full text-sm bg-gray-50 text-gray-900 rounded-lg border border-gray-300 focus:ring-gray-500 focus:border-gray-500 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-gray-500 dark:focus:border-gray-500"
46+
></textarea>
47+
</div>
48+
49+
<div class="mb-6">
50+
<label for="message" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Categorías *</label>
51+
<select id="categories" name="state[]" multiple placeholder="Categorías" autocomplete="off">
52+
<option value="">Select a state...</option>
53+
{categoriesList.map((category: any) => <option value={category.key}>{category.text}</option>)}
54+
</select>
55+
</div>
56+
57+
<div class="grid gap-6 mb-6 md:grid-cols-3">
58+
<CheckboxInput id="fulltime" label="Tiempo Competo" />
59+
<CheckboxInput id="parttime" label="Tiempo Parcial" />
60+
<CheckboxInput id="freelance" label="Freelance" />
61+
</div>
62+
<div class="grid gap-6 mb-6 md:grid-cols-3">
63+
<CheckboxInput id="remote" label="Remoto" />
64+
<CheckboxInput id="presential" label="Presencial" />
65+
<CheckboxInput id="relocate" label="Reubicación" />
66+
</div>
67+
68+
<div class="grid gap-6 mb-6 md:grid-cols-1">
69+
<TextInput id="location" label="Ubicación" maxlength="250" />
70+
</div>
71+
<div class="grid gap-6 mb-6 md:grid-cols-2">
72+
<TextInput id="organization" label="Empresa" maxlength="250" type="text" />
73+
<TextInput id="organizationWebsite" label="Sitio web de la empresa" maxlength="300" type="url" />
74+
</div>
75+
76+
<div class="flex justify-between">
77+
<h1 class="text-xl font-bold dark:text-gray-100">Datos de Contacto</h1>
78+
</div>
79+
<hr class="my-4" />
80+
<div class="grid gap-6 mb-6 md:grid-cols-2">
81+
<TextInput id="applyEmail" label="Correo Electronico" maxlength="250" type="email" />
82+
<TextInput id="applyUrl" label="URL para aplicar" maxlength="250" type="url" />
83+
<UsernameInput id="applyTelegramUser" label="Usuario de Telegram" maxlength="250" />
84+
<TextInput id="applyPhone" label="Número Telefónico" maxlength="250" type="tel" />
85+
</div>
86+
</div>
87+
88+
<div class="flex justify-between items-center mt-4">
89+
<button
90+
type="button"
91+
id="deleteJobBtn"
92+
class="text-red-700 hover:text-white border border-red-700 hover:bg-red-800 focus:ring-4 focus:outline-none focus:ring-red-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:border-red-500 dark:text-red-500 dark:hover:text-white dark:hover:bg-red-600 dark:focus:ring-red-900"
93+
>
94+
Eliminar Empleo
95+
</button>
96+
97+
<button
98+
id="updateJobBtn"
99+
class="text-white bg-gray-800 hover:bg-gray-900 focus:ring-4 focus:outline-none focus:ring-gray-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-gray-600 dark:hover:bg-gray-700 dark:focus:ring-gray-800"
100+
>
101+
Guardar Cambios
102+
</button>
103+
</div>
104+
</form>
105+
106+
<Toast />
107+
</div>
108+
</BaseLayout>
109+
110+
<link href="https://cdn.jsdelivr.net/npm/tom-select/dist/css/tom-select.css" rel="stylesheet" />
111+
<script is:inline src="https://cdn.jsdelivr.net/npm/tom-select/dist/js/tom-select.complete.min.js"></script>
112+
113+
<script is:inline>
114+
var control = new TomSelect("#categories", { maxItems: 5 });
115+
</script>
116+
117+
<script>
118+
import { backend_url } from "../data/conf.json";
119+
import { showToast } from "../components/toast/toast";
120+
121+
const urlParams = new URLSearchParams(window.location.search);
122+
const jobId = urlParams.get("id");
123+
const jobForm = document.getElementById("job-form");
124+
const deleteJobBtn = document.getElementById("deleteJobBtn");
125+
126+
if (jobForm) jobForm.addEventListener("submit", updateJob, true);
127+
if (deleteJobBtn) deleteJobBtn.addEventListener("click", deleteJob, true);
128+
129+
// Load Job Logic
130+
window.addEventListener("load", async () => {
131+
if (!jobId) return;
132+
try {
133+
const response = await fetch(`${backend_url}api/jobs/get-job?id=${jobId}`);
134+
if (!response.ok) throw new Error("Failed to load job");
135+
const data = await response.json();
136+
populateForm(data);
137+
} catch (e) {
138+
showToast("Error cargando la información del empleo.", true);
139+
console.error(e);
140+
}
141+
});
142+
143+
function populateForm(data) {
144+
if (!data) return;
145+
146+
setValue("title", data.title);
147+
setValue("salary", data.salary);
148+
setValue("description", data.description);
149+
setValue("location", data.location);
150+
setValue("organization", data.organization);
151+
setValue("organizationWebsite", data.organizationWebsite);
152+
setValue("applyEmail", data.applyEmail);
153+
setValue("applyUrl", data.applyUrl);
154+
setValue("applyTelegramUser", data.applyTelegramUser);
155+
setValue("applyPhone", data.applyPhone);
156+
157+
setChecked("fulltime", data.fulltime);
158+
setChecked("parttime", data.parttime);
159+
setChecked("freelance", data.freelance);
160+
setChecked("remote", data.remote);
161+
setChecked("presential", data.presential);
162+
setChecked("relocate", data.relocate);
163+
164+
if (data.categories && Array.isArray(data.categories)) {
165+
data.categories.forEach((cat) => (window as any).control.addItem(cat));
166+
}
167+
}
168+
169+
function setValue(id: string, value: string) {
170+
const elem = document.getElementById(id) as HTMLInputElement;
171+
if (elem && value !== undefined) elem.value = value;
172+
}
173+
174+
function setChecked(id: string, checked: boolean) {
175+
const elem = document.getElementById(id) as HTMLInputElement;
176+
if (elem && checked !== undefined) elem.checked = checked;
177+
}
178+
179+
async function updateJob(e) {
180+
e.preventDefault();
181+
const data = createJobData();
182+
183+
if (!data.applyEmail && !data.applyUrl && !data.applyTelegramUser && !data.applyPhone) {
184+
showToast("Debe ingresar al menos un método de contacto (Email, URL, Telegram o Teléfono).", true);
185+
return;
186+
}
187+
188+
disableBtn();
189+
try {
190+
const response = await fetch(`${backend_url}api/jobs/${jobId}`, {
191+
method: "PUT",
192+
body: JSON.stringify(data),
193+
headers: { "Content-Type": "application/json" },
194+
});
195+
if (!response.ok) throw new Error("Update failed");
196+
window.open(`${window.location.origin}/empleo-editado`, "_self");
197+
} catch (e) {
198+
enableBtn();
199+
showToast("Error actualizando el empleo. Intente más tarde.", true);
200+
}
201+
}
202+
203+
async function deleteJob() {
204+
if (!confirm("¿Estás seguro de que quieres eliminar este empleo? Esta acción no se puede deshacer.")) return;
205+
206+
try {
207+
const response = await fetch(`${backend_url}api/jobs/${jobId}`, { method: "DELETE" });
208+
if (!response.ok) throw new Error("Delete failed");
209+
window.open(`${window.location.origin}/empleo-eliminado`, "_self");
210+
} catch (e) {
211+
showToast("Error eliminando el empleo. Intente más tarde.", true);
212+
}
213+
}
214+
215+
function getElem(id: string) {
216+
return document.getElementById(id) as HTMLInputElement;
217+
}
218+
219+
function createJobData() {
220+
let data = {
221+
title: getElem("title").value,
222+
salary: getElem("salary").value,
223+
description: getElem("description").value,
224+
categories: [] as string[],
225+
freelance: getElem("freelance").checked,
226+
fulltime: getElem("fulltime").checked,
227+
parttime: getElem("parttime").checked,
228+
remote: getElem("remote").checked,
229+
presential: getElem("presential").checked,
230+
relocate: getElem("relocate").checked,
231+
location: getElem("location").value,
232+
applyEmail: getElem("applyEmail").value,
233+
applyUrl: getElem("applyUrl").value,
234+
applyTelegramUser: getElem("applyTelegramUser").value,
235+
applyPhone: getElem("applyPhone").value,
236+
organization: getElem("organization").value,
237+
organizationWebsite: getElem("organizationWebsite").value,
238+
slug: getElem("title").value.replace(/[^a-zA-Z0-9]+/g, "-"),
239+
};
240+
241+
const categoriesElem = document.getElementById("categories") as HTMLSelectElement;
242+
if (categoriesElem) {
243+
for (let option of categoriesElem.options) {
244+
if (option.selected) {
245+
data.categories.push(option.value);
246+
}
247+
}
248+
}
249+
return data;
250+
}
251+
252+
function disableBtn() {
253+
const btn = document.getElementById("updateJobBtn") as HTMLButtonElement;
254+
if (btn) {
255+
btn.disabled = true;
256+
btn.innerText = "Guardando...";
257+
}
258+
}
259+
260+
function enableBtn() {
261+
const btn = document.getElementById("updateJobBtn") as HTMLButtonElement;
262+
if (btn) {
263+
btn.disabled = false;
264+
btn.innerText = "Guardar Cambios";
265+
}
266+
}
267+
</script>

src/pages/empleo-editado.astro

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
---
2+
import BaseLayout from "../layouts/baseLayout.astro";
3+
4+
const title = "Empleo actualizado satisfactoriamente!";
5+
const description = "Tu oferta de empleo ha sido actualizada con éxito.";
6+
---
7+
8+
<BaseLayout title={title} description={description}>
9+
<div
10+
class="bg-white border md:mx-auto mb-5 -mt-6 md:mt-12 lg:max-w-[58rem] border-gray-200 rounded-lg dark:bg-gray-800 dark:border-gray-700 w-full"
11+
>
12+
<div class="p-8 md:px-15 lg:px-20">
13+
<h1 class="my-10 text-3xl md:text-4xl text-center font-bold tracking-tight text-gray-900 dark:text-white">
14+
{title}
15+
</h1>
16+
<article class="my-5 prose dark:prose-invert lg:prose-lg prose-img:mx-auto max-w-none break-words text-center">
17+
<p>Los cambios reralizados en su oferta de empleo se han guardado correctamente.</p>
18+
<a href="/" class="text-blue-600 hover:text-blue-800 underline">Volver al inicio</a>
19+
</article>
20+
</div>
21+
</div>
22+
</BaseLayout>

src/pages/empleo-eliminado.astro

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
---
2+
import BaseLayout from "../layouts/baseLayout.astro";
3+
4+
const title = "Empleo eliminado satisfactoriamente";
5+
const description = "Tu oferta de empleo ha sido eliminada con éxito.";
6+
---
7+
8+
<BaseLayout title={title} description={description}>
9+
<div
10+
class="bg-white border md:mx-auto mb-5 -mt-6 md:mt-12 lg:max-w-[58rem] border-gray-200 rounded-lg dark:bg-gray-800 dark:border-gray-700 w-full"
11+
>
12+
<div class="p-8 md:px-15 lg:px-20">
13+
<h1 class="my-10 text-3xl md:text-4xl text-center font-bold tracking-tight text-gray-900 dark:text-white">
14+
{title}
15+
</h1>
16+
<article class="my-5 prose dark:prose-invert lg:prose-lg prose-img:mx-auto max-w-none break-words text-center">
17+
<p>Su oferta de empleo ha sido eliminada correctamente y ya no será visible en el sitio.</p>
18+
<a href="/" class="text-blue-600 hover:text-blue-800 underline">Volver al inicio</a>
19+
</article>
20+
</div>
21+
</div>
22+
</BaseLayout>

0 commit comments

Comments
 (0)