Sort of working beta
This commit is contained in:
@ -3,8 +3,8 @@ import { appName } from '@/app.ts'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<router-link class="flex justify-center items-center gap-3" to="/">
|
||||
<Link class="flex justify-center items-center gap-3" href="/">
|
||||
<img src="@/Assets/logo.png" alt="logo DatBrowser" class="h-20" />
|
||||
<h1 class="text-xl font-display select-none">{{ appName }}</h1>
|
||||
</router-link>
|
||||
</Link>
|
||||
</template>
|
||||
|
@ -1,4 +1,8 @@
|
||||
<script setup lang="ts">
|
||||
import Description from '@/Components/ui/text/Description.vue';
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h2 class="text-xl"><slot name="title" /></h2>
|
||||
<p class="text-dark-green"><slot name="description" /></p>
|
||||
<Description><slot name="description" /></Description>
|
||||
</template>
|
||||
|
139
resources/js/Components/Layout/Job/JobForm.vue
Normal file
139
resources/js/Components/Layout/Job/JobForm.vue
Normal file
@ -0,0 +1,139 @@
|
||||
<script setup lang="ts">
|
||||
import { Job, JobArtifact, JobInfo, JobInfoType, JobRunArtifact } from "@/types/Jobs/job";
|
||||
import { router } from "@inertiajs/vue3";
|
||||
import { onMounted, reactive, ref, watch } from "vue";
|
||||
import Button from "../../ui/button/Button.vue";
|
||||
import JobFormField from "./JobFormField.vue";
|
||||
import LoadingSpinner from "@/Components/ui/feedback/spinner/LoadingSpinner.vue";
|
||||
import { httpApi } from "@/lib/utils";
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
job: Job;
|
||||
error?: string;
|
||||
}>(), {
|
||||
error: "",
|
||||
});
|
||||
|
||||
const isSaving = ref(false);
|
||||
const isTesting = ref(false);
|
||||
|
||||
const errorMessage = ref(props.error);
|
||||
|
||||
const jobSuccess = ref(true);
|
||||
|
||||
function submit() {
|
||||
isSaving.value = true;
|
||||
|
||||
if (!testForm()) {
|
||||
isTesting.value = false;
|
||||
return;
|
||||
}
|
||||
|
||||
router.patch("/jobs/" + props.job.id, {
|
||||
is_active: isActiveJobInfo.value.value,
|
||||
...props.job.job_infos.reduce((acc, jobInfo) => {
|
||||
acc[jobInfo.id] = jobInfo.value;
|
||||
return acc;
|
||||
}, {} as Record<number, string | boolean>),
|
||||
});
|
||||
setTimeout(() => {
|
||||
isSaving.value = false;
|
||||
}, 200);
|
||||
}
|
||||
|
||||
const isActiveJobInfo = ref<JobInfo>({
|
||||
id: 0,
|
||||
name: "Activer",
|
||||
description: "Activer le job",
|
||||
value: props.job.is_active,
|
||||
is_required: false,
|
||||
job_info_type: { name: "checkbox" } as JobInfoType,
|
||||
} as JobInfo);
|
||||
|
||||
async function testJob() {
|
||||
isTesting.value = true;
|
||||
|
||||
submit();
|
||||
|
||||
console.log("Testing job", props.job.id);
|
||||
let response;
|
||||
try {
|
||||
response = await httpApi<{ artifact?: JobRunArtifact }>(
|
||||
`/jobs/${props.job.id}/test`
|
||||
);
|
||||
jobSuccess.value = response.artifact?.success ?? false;
|
||||
} catch (e) {
|
||||
console.error("Testing the job failed : ", e);
|
||||
jobSuccess.value = false;
|
||||
} finally {
|
||||
isTesting.value = false;
|
||||
}
|
||||
|
||||
if (response?.artifact) {
|
||||
alert(response.artifact.artifacts[0].name + " : " + response.artifact.artifacts[0].content);
|
||||
}
|
||||
}
|
||||
|
||||
function testForm(): boolean {
|
||||
console.log("Testing the form validity");
|
||||
const jobForm = document.querySelector('form[name="jobForm"]') as HTMLFormElement;
|
||||
if (jobForm.checkValidity() == false) {
|
||||
console.log("Form is not valid");
|
||||
if (jobForm.reportValidity) {
|
||||
jobForm.reportValidity()
|
||||
}
|
||||
else {
|
||||
errorMessage.value = "Le formulaire n'est pas valide";
|
||||
}
|
||||
isTesting.value = false;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<form @submit.prevent="submit" class="flex p-3 flex-col gap-4" name="jobForm">
|
||||
<JobFormField v-if="job.id != 1" :jobInfo="isActiveJobInfo" />
|
||||
|
||||
<JobFormField
|
||||
:jobInfo="jobInfo"
|
||||
v-for="jobInfo of job.job_infos"
|
||||
v-key="jobInfo.id"
|
||||
/>
|
||||
|
||||
<Transition>
|
||||
<p v-if="errorMessage" class="text-destructive">{{ errorMessage }}</p>
|
||||
</Transition>
|
||||
|
||||
<Button
|
||||
type="submit"
|
||||
variant="secondary"
|
||||
:disabled="isSaving || isTesting"
|
||||
>
|
||||
Enregistrer
|
||||
<LoadingSpinner v-if="isSaving" />
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="outline"
|
||||
:disabled="isSaving || isTesting"
|
||||
@click.prevent="testJob"
|
||||
>
|
||||
Tester
|
||||
<LoadingSpinner v-if="isTesting" />
|
||||
</Button>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
/* we will explain what these classes do next! */
|
||||
.v-enter-active,
|
||||
.v-leave-active {
|
||||
transition: opacity 0.5s ease;
|
||||
}
|
||||
|
||||
.v-enter-from,
|
||||
.v-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
26
resources/js/Components/Layout/Job/JobFormField.vue
Normal file
26
resources/js/Components/Layout/Job/JobFormField.vue
Normal file
@ -0,0 +1,26 @@
|
||||
<script setup lang="ts">
|
||||
import Checkbox from '@/Components/ui/checkbox/Checkbox.vue';
|
||||
import VModelCheckbox from '@/Components/ui/checkbox/VModelCheckbox.vue';
|
||||
import Input from '@/Components/ui/input/Input.vue';
|
||||
import { Label } from '@/Components/ui/label';
|
||||
import Description from '@/Components/ui/text/Description.vue';
|
||||
import { JobInfo } from '@/types/Jobs/job';
|
||||
import { watch } from 'vue';
|
||||
|
||||
const props = defineProps<{
|
||||
jobInfo: JobInfo;
|
||||
}>();
|
||||
|
||||
const jobInfoType = props.jobInfo.job_info_type.name;
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<Label :for="'' + jobInfo.id" class="text">{{ jobInfo.name }}<span v-if="jobInfo.is_required" class="cursor-help" title="Requis" aria-label="Requis">*</span></Label>
|
||||
<Description>{{ jobInfo.description }}</Description>
|
||||
<Input v-if="jobInfoType != 'checkbox'" :type="jobInfoType" :id="'' + jobInfo.id" :name="'' + jobInfo.id" :placeholder="jobInfo.placeholder" v-model="jobInfo.value as string" :required="jobInfo.is_required" />
|
||||
<VModelCheckbox v-else :id="'' + jobInfo.id" :class="''" v-model="jobInfo.value as boolean" />
|
||||
</div>
|
||||
|
||||
|
||||
</template>
|
@ -8,7 +8,7 @@ defineProps<{
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<MainNavItem :link="`/job/${job.id}`">
|
||||
<MainNavItem :link="`/jobs/${job.id}`">
|
||||
{{ job.name }}
|
||||
</MainNavItem>
|
||||
</template>
|
||||
|
@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { cn } from '@/lib/utils.ts'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { Primitive, type PrimitiveProps } from 'radix-vue'
|
||||
import { type ButtonVariants, buttonVariants } from '.'
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { cn } from '@/lib/utils.ts'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes['class']
|
||||
|
@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { cn } from '@/lib/utils.ts'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes['class']
|
||||
|
@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { cn } from '@/lib/utils.ts'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes['class']
|
||||
|
@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { cn } from '@/lib/utils.ts'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes['class']
|
||||
|
@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { cn } from '@/lib/utils.ts'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes['class']
|
||||
|
@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { cn } from '@/lib/utils.ts'
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes['class']
|
||||
|
33
resources/js/Components/ui/checkbox/Checkbox.vue
Normal file
33
resources/js/Components/ui/checkbox/Checkbox.vue
Normal file
@ -0,0 +1,33 @@
|
||||
<script setup lang="ts">
|
||||
import type { CheckboxRootEmits, CheckboxRootProps } from 'radix-vue'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { Check } from 'lucide-vue-next'
|
||||
import { CheckboxIndicator, CheckboxRoot, useForwardPropsEmits } from 'radix-vue'
|
||||
import { computed, type HTMLAttributes } from 'vue'
|
||||
|
||||
const props = defineProps<CheckboxRootProps & { class?: HTMLAttributes['class'] }>()
|
||||
const emits = defineEmits<CheckboxRootEmits>()
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props
|
||||
|
||||
return delegated
|
||||
})
|
||||
|
||||
const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<CheckboxRoot
|
||||
v-bind="forwarded"
|
||||
:class="
|
||||
cn('peer h-4 w-4 shrink-0 rounded-sm border border-primary ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground',
|
||||
props.class)"
|
||||
>
|
||||
<CheckboxIndicator class="flex h-full w-full items-center justify-center text-current">
|
||||
<slot>
|
||||
<Check class="h-4 w-4" />
|
||||
</slot>
|
||||
</CheckboxIndicator>
|
||||
</CheckboxRoot>
|
||||
</template>
|
15
resources/js/Components/ui/checkbox/VModelCheckbox.vue
Normal file
15
resources/js/Components/ui/checkbox/VModelCheckbox.vue
Normal file
@ -0,0 +1,15 @@
|
||||
<script setup lang="ts">
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
class?: string;
|
||||
}>(), {
|
||||
class: '',
|
||||
});
|
||||
|
||||
const model = defineModel<boolean>({type: Boolean, default: false});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<input type="checkbox" :checked="model" @click="() => (model = !model)" class="peer h-7 w-7 shrink-0 rounded-sm border border-gray ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 checked:bg-secondary checked:text-dark-green transition cursor-pointer">
|
||||
</template>
|
1
resources/js/Components/ui/checkbox/index.ts
Normal file
1
resources/js/Components/ui/checkbox/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export { default as Checkbox } from './Checkbox.vue'
|
@ -0,0 +1,33 @@
|
||||
<script setup lang="ts">
|
||||
import { Loader2 } from 'lucide-vue-next'
|
||||
import type { PrimitiveProps } from 'radix-vue'
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { spinnerVariants, type SpinnerVariants } from '.'
|
||||
|
||||
interface Props extends PrimitiveProps {
|
||||
size?: SpinnerVariants['size']
|
||||
class?: HTMLAttributes['class']
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
:class="cn(spinnerVariants({ size }), props.class)"
|
||||
>
|
||||
<path d="M21 12a9 9 0 1 1-6.219-8.56"></path>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
19
resources/js/Components/ui/feedback/spinner/index.ts
Normal file
19
resources/js/Components/ui/feedback/spinner/index.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { cva, type VariantProps } from 'class-variance-authority'
|
||||
|
||||
export const spinnerVariants = cva('animate-spin lucide lucide-loader-circle-icon', {
|
||||
variants: {
|
||||
size: {
|
||||
default: 'w-4 h-4 m-2',
|
||||
xs: 'w-1 h-1 m-1',
|
||||
sm: 'w-2 h-2 m-1',
|
||||
lg: 'w-7 h-7 m-3',
|
||||
xl: 'w-12 h-12 m-4',
|
||||
icon: 'w-10 h-10 m-4'
|
||||
}
|
||||
},
|
||||
defaultVariants: {
|
||||
size: 'default'
|
||||
}
|
||||
})
|
||||
|
||||
export type SpinnerVariants = VariantProps<typeof spinnerVariants>
|
24
resources/js/Components/ui/input/Input.vue
Normal file
24
resources/js/Components/ui/input/Input.vue
Normal file
@ -0,0 +1,24 @@
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { useVModel } from '@vueuse/core'
|
||||
|
||||
const props = defineProps<{
|
||||
defaultValue?: string | number
|
||||
modelValue?: string | number
|
||||
class?: HTMLAttributes['class']
|
||||
}>()
|
||||
|
||||
const emits = defineEmits<{
|
||||
(e: 'update:modelValue', payload: string | number): void
|
||||
}>()
|
||||
|
||||
const modelValue = useVModel(props, 'modelValue', emits, {
|
||||
passive: true,
|
||||
defaultValue: props.defaultValue,
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<input v-model="modelValue" :class="cn('flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50', props.class)">
|
||||
</template>
|
1
resources/js/Components/ui/input/index.ts
Normal file
1
resources/js/Components/ui/input/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export { default as Input } from './Input.vue'
|
27
resources/js/Components/ui/label/Label.vue
Normal file
27
resources/js/Components/ui/label/Label.vue
Normal file
@ -0,0 +1,27 @@
|
||||
<script setup lang="ts">
|
||||
import { cn } from '@/lib/utils'
|
||||
import { Label, type LabelProps } from 'radix-vue'
|
||||
import { computed, type HTMLAttributes } from 'vue'
|
||||
|
||||
const props = defineProps<LabelProps & { class?: HTMLAttributes['class'] }>()
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props
|
||||
|
||||
return delegated
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Label
|
||||
v-bind="delegatedProps"
|
||||
:class="
|
||||
cn(
|
||||
'text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70',
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
>
|
||||
<slot />
|
||||
</Label>
|
||||
</template>
|
1
resources/js/Components/ui/label/index.ts
Normal file
1
resources/js/Components/ui/label/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export { default as Label } from './Label.vue'
|
3
resources/js/Components/ui/text/Description.vue
Normal file
3
resources/js/Components/ui/text/Description.vue
Normal file
@ -0,0 +1,3 @@
|
||||
<template>
|
||||
<p class="text-dark-green"><slot/></p>
|
||||
</template>
|
@ -4,12 +4,17 @@ import BaseLayout from "./BaseLayout.vue";
|
||||
import MainNavJobLink from "@/Components/Layout/MainNav/MainNavJobLink.vue";
|
||||
import { Job } from "@/types/Jobs/job";
|
||||
import MainNavItem from "@/Components/Layout/MainNav/MainNavItem.vue";
|
||||
import { httpApi } from "@/lib/utils";
|
||||
import { onMounted, ref } from "vue";
|
||||
|
||||
let jobs = [
|
||||
{ id: 1, name: "Hellcase" },
|
||||
{ id: 2, name: "Jeu gratuit Epic Games" },
|
||||
{ id: 3, name: "Envoyer un post instagram" },
|
||||
];
|
||||
const jobs = ref<Job[]>([]);
|
||||
|
||||
async function fetchJobs() {
|
||||
let jobsRaw = await httpApi<Job[]>("/jobs");
|
||||
jobs.value = jobsRaw.sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime());
|
||||
}
|
||||
|
||||
onMounted(fetchJobs);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@ -19,16 +24,16 @@ let jobs = [
|
||||
<ul class="flex flex-col gap-2">
|
||||
<MainNavItem link="/"> Accueil </MainNavItem>
|
||||
<MainNavJobLink
|
||||
:job="job as Job"
|
||||
v-for="job in jobs"
|
||||
:key="job.id"
|
||||
:job="job"
|
||||
/>
|
||||
</ul>
|
||||
</nav>
|
||||
</ScrollArea>
|
||||
|
||||
<ScrollArea class="flex-1 h-full overflow-auto">
|
||||
<main>
|
||||
<main class="p-3">
|
||||
<slot />
|
||||
</main>
|
||||
</ScrollArea>
|
||||
|
@ -1,10 +1,12 @@
|
||||
<script setup lang="ts">
|
||||
import JobCard from '../Components/ui/job/JobCard.vue'
|
||||
import JobForm from '../Components/Layout/Job/JobForm.vue'
|
||||
import JobCard from '../Components/Layout/Job/JobCard.vue'
|
||||
import { Job } from "@/types/Jobs/job";
|
||||
import { Head } from "@inertiajs/vue3";
|
||||
|
||||
defineProps<{
|
||||
job: Job;
|
||||
error?: string;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
@ -12,4 +14,6 @@ defineProps<{
|
||||
<Head title="Welcome" />
|
||||
|
||||
<JobCard :job="job" />
|
||||
|
||||
<JobForm :job="job" :error="error" />
|
||||
</template>
|
||||
|
@ -4,3 +4,14 @@ import { twMerge } from "tailwind-merge"
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs))
|
||||
}
|
||||
|
||||
|
||||
export async function httpApi<T>(route: string): Promise<T> {
|
||||
let response = await fetch(import.meta.env.VITE_APP_URL + "/api" + route);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
return response.json();
|
||||
}
|
||||
|
34
resources/js/types/Jobs/job.d.ts
vendored
34
resources/js/types/Jobs/job.d.ts
vendored
@ -3,5 +3,39 @@ export type Job = {
|
||||
name: string;
|
||||
description: string;
|
||||
is_active: boolean;
|
||||
|
||||
job_infos: JobInfo[];
|
||||
|
||||
created_at: Date;
|
||||
}
|
||||
|
||||
export type JobInfo = {
|
||||
id: number;
|
||||
|
||||
name: string;
|
||||
description: string;
|
||||
placeholder: string;
|
||||
|
||||
value: string | boolean;
|
||||
is_required: boolean;
|
||||
job_info_type: JobInfoType;
|
||||
|
||||
job_id: number;
|
||||
}
|
||||
|
||||
export type JobInfoType = {
|
||||
id: number;
|
||||
name: string;
|
||||
created_at: Date;
|
||||
}
|
||||
|
||||
export type JobRunArtifact = {
|
||||
jobId: number;
|
||||
artifacts: JobArtifact[];
|
||||
success: boolean;
|
||||
}
|
||||
|
||||
export type JobArtifact = {
|
||||
name: string;
|
||||
content: string;
|
||||
}
|
||||
|
@ -12,7 +12,11 @@
|
||||
|
||||
<!-- Scripts -->
|
||||
@routes
|
||||
@vite(['resources/js/app.ts', "resources/js/Pages/{$page['component']}.vue"])
|
||||
@if(config('app.env') === 'production')
|
||||
@vite(['resources/js/app.ts'])
|
||||
@else
|
||||
@vite(['resources/js/app.ts', "resources/js/Pages/{$page['component']}.vue"])
|
||||
@endif
|
||||
@inertiaHead
|
||||
</head>
|
||||
<body class="font-sans antialiased">
|
||||
|
Reference in New Issue
Block a user