Initial commit: PC Builder AdonisJS project

This commit is contained in:
Kevin
2026-06-28 10:41:51 +02:00
commit 648d0af538
144 changed files with 18570 additions and 0 deletions
+171
View File
@@ -0,0 +1,171 @@
<script setup lang="ts">
import { computed, reactive } from 'vue'
import { Link, usePage } from '@inertiajs/vue3'
const { props } = usePage<{
components: any[]
states: any[]
errors: any[]
}>()
const { components, states } = props
const errors = computed(() => props.errors || [])
const componentTypeOrder = [
'Processor',
'Motherboard',
'Graphic Card',
'Memory',
'SSD',
'Storage',
'Cooler',
'Power Supply',
'Case',
'WiFi-Card',
'Fan',
'Other',
]
const componentsByType = computed(() => {
const groups = new Map<string, any[]>()
components.forEach((component) => {
if (!groups.has(component.type.name)) {
groups.set(component.type.name, [])
}
groups.get(component.type.name)!.push(component)
})
return Array.from(groups)
.map(([type, components]) => ({ type, components }))
.sort((a, b) => componentTypeOrder.indexOf(a.type) - componentTypeOrder.indexOf(b.type))
})
const form = reactive({
title: '',
sellPrice: 0,
gpuScore: 0,
cpuScore: 0,
stateId: 1,
globalScore: 0,
componentsId: [] as number[],
})
function getErrorMessage(field: string): string | null {
const error = errors.value.find((error) => error.field === field)
return error ? error.message : null
}
</script>
<template>
<div class="max-w-4xl mx-auto p-8 bg-white shadow-lg rounded-lg">
<h1 class="text-2xl font-semibold mb-8 text-gray-800">Create New Computer</h1>
<form class="space-y-6">
<div class="mt-2">
<label class="block text-gray-700 text-sm font-bold mb-2" for="title">Title</label>
<input
name="title"
v-model="form.title"
class="w-full mt-2 border-gray-200 focus:border-indigo-600 focus:ring focus:ring-indigo-500/40 px-3 py-2 border rounded text-gray-700"
type="text"
/>
</div>
<div class="mt-2 grid grid-cols-3 gap-4">
<div>
<label class="block text-gray-700 text-sm font-bold mb-2" for="gpuScore">GPU Score</label>
<input
name="gpuScore"
v-model="form.gpuScore"
class="w-full border-gray-200 focus:border-indigo-600 focus:ring focus:ring-indigo-500/40 px-3 py-2 border rounded text-gray-700"
type="number"
/>
</div>
<div>
<label class="block text-gray-700 text-sm font-bold mb-2" for="cpuScore">CPU Score</label>
<input
name="cpuScore"
v-model="form.cpuScore"
class="w-full border-gray-200 focus:border-indigo-600 focus:ring focus:ring-indigo-500/40 px-3 py-2 border rounded text-gray-700"
type="number"
/>
</div>
<div>
<label class="block text-gray-700 text-sm font-bold mb-2" for="globalScore"
>Global Score</label
>
<input
name="globalScore"
v-model="form.globalScore"
class="w-full border-gray-200 focus:border-indigo-600 focus:ring focus:ring-indigo-500/40 px-3 py-2 border rounded text-gray-700"
type="number"
/>
</div>
</div>
<div class="mt-2">
<label class="block text-gray-700 text-sm font-bold mb-2" for="components"
>Components</label
>
<div v-for="{ type, components } in componentsByType" :key="type">
<label :for="`select-${type}`" class="block text-gray-700 text-sm font-bold mb-1 mt-2">
{{ type }}
</label>
<div class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4">
<div
v-for="component in components"
:key="component.id"
class="flex items-center mt-1"
>
<input
type="checkbox"
:id="`checkbox-${component.id}`"
:value="component.id"
v-model="form.componentsId"
class="mr-2"
/>
<label :for="`checkbox-${component.id}`" class="text-sm text-gray-700">
{{ component.name }}
</label>
</div>
</div>
</div>
</div>
<div class="mt-2 grid grid-cols-3 gap-4">
<div class="pt-2">
<label class="block text-gray-700 text-sm font-bold mb-2" for="components">State</label>
<select
v-model="form.stateId"
class="w-full mt-2 border-gray-200 focus:border-indigo-600 focus:ring-indigo-500 px-3 py-2.5 focus:ring-1 border rounded text-gray-700 bg-white"
>
<option disabled value="">Please select one</option>
<option v-for="state in states" :key="state.id" :value="state.id">
{{ state.name }}
</option>
</select>
</div>
<div />
<div class="mt-2">
<label class="block text-gray-700 text-sm font-bold mb-2" for="sellPrice"
>Sell Price</label
>
<input
v-model="form.sellPrice"
class="w-full mt-2 border-gray-200 focus:border-indigo-600 focus:ring focus:ring-indigo-500/40 px-3 py-2 border rounded text-gray-700"
type="number"
step="0.01"
/>
</div>
</div>
<Link
href="/computers"
type="button"
as="button"
method="post"
:data="form"
class="px-4 py-2 bg-indigo-600 text-white rounded hover:bg-blue-700 transition"
>
Create
</Link>
</form>
</div>
</template>
<style scoped></style>
+127
View File
@@ -0,0 +1,127 @@
<script setup lang="ts">
import { computed, reactive } from 'vue'
import { Link, router, usePage } from '@inertiajs/vue3'
const { props } = usePage<{
computer: any
availableComponents: any[]
states: any[]
errors?: any[]
}>()
const { computer, availableComponents, states } = props
const errors = computed(() => props.errors || [])
const form = reactive({
id: computer.id,
title: computer.title,
sellPrice: computer.sellPrice,
gpuScore: computer.gpuScore,
cpuScore: computer.cpuScore,
globalScore: computer.globalScore,
stateId: computer.state.id,
componentsId: computer.components.map((c: any) => c.id),
})
function updateComponents(componentsId: number[]) {
form.componentsId = componentsId
}
function deleteComputer(id: number) {
router.delete(`/computers/${id}`)
}
function getErrorMessage(field: string): string | null {
const error = errors.value.find((error) => error.field === field)
return error ? error.message : null
}
</script>
<template>
<div class="max-w-4xl mx-auto p-8 bg-white shadow-lg rounded-lg">
<h1 class="text-2xl font-semibold mb-8 text-gray-800">Edit Computer</h1>
<form class="space-y-6">
<div>
<label class="block text-gray-700 text-sm font-bold mb-2" for="title">Title</label>
<input
v-model="form.title"
class="w-full mt-2 border-gray-200 focus:border-indigo-600 focus:ring focus:ring-indigo-500/40 px-3 py-2 border rounded text-gray-700"
type="text"
/>
</div>
<div>
<label class="block text-gray-700 text-sm font-bold mb-2" for="sellPrice">Sell Price</label>
<input
name="sell_price"
v-model="form.sellPrice"
class="w-full mt-2 border-gray-200 focus:border-indigo-600 focus:ring focus:ring-indigo-500/40 px-3 py-2 border rounded text-gray-700"
type="number"
step="0.01"
/>
</div>
<div class="grid grid-cols-3 gap-4">
<div>
<label class="block text-gray-700 text-sm font-bold mb-2" for="gpuScore">GPU Score</label>
<input
v-model="form.gpuScore"
class="w-full border-gray-200 focus:border-indigo-600 focus:ring focus:ring-indigo-500/40 px-3 py-2 border rounded text-gray-700"
type="number"
/>
</div>
<div>
<label class="block text-gray-700 text-sm font-bold mb-2" for="cpuScore">CPU Score</label>
<input
v-model="form.cpuScore"
class="w-full border-gray-200 focus:border-indigo-600 focus:ring focus:ring-indigo-500/40 px-3 py-2 border rounded text-gray-700"
type="number"
/>
</div>
<div>
<label class="block text-gray-700 text-sm font-bold mb-2" for="globalScore"
>Global Score</label
>
<input
v-model="form.globalScore"
class="w-full border-gray-200 focus:border-indigo-600 focus:ring focus:ring-indigo-500/40 px-3 py-2 border rounded text-gray-700"
type="number"
/>
</div>
</div>
<div class="pt-2">
<label class="block text-gray-700 text-sm font-bold mb-2" for="components">State</label>
<select
v-model="form.stateId"
class="w-full mt-2 border-gray-200 focus:border-indigo-600 focus:ring-indigo-500 px-3 py-2.5 focus:ring-1 border rounded text-gray-700 bg-white"
>
<option disabled value="">Please select one</option>
<option v-for="state in states" :key="state.id" :value="state.id">
{{ state.name }}
</option>
</select>
</div>
<div
class="flex flex-col md:flex-row items-stretch justify-between space-y-2 md:space-y-0 md:space-x-2"
>
<Link
:href="`/computers/${form.id}`"
method="put"
:data="form"
as="button"
class="w-full md:w-auto px-4 py-2 bg-indigo-600 text-white rounded hover:bg-blue-700 transition flex justify-center"
>
Save
</Link>
<button
@click="deleteComputer(computer.id)"
class="w-full md:w-auto px-4 py-2 bg-gray-300 text-white rounded hover:bg-red-500 transition flex items-center justify-center"
>
Delete
</button>
</div>
</form>
</div>
</template>
<style scoped></style>
+128
View File
@@ -0,0 +1,128 @@
<script setup lang="ts">
import { Link } from '@inertiajs/vue3'
defineProps<{ computers: any[] }>()
</script>
<template>
<div>
<div class="flex justify-between items-center">
<h3 class="text-3xl font-semibold text-gray-700">Computers</h3>
<Link
href="/computers/create"
type="button"
as="button"
class="px-4 py-2 bg-indigo-600 text-white rounded hover:bg-blue-700 transition"
>
Add
</Link>
</div>
<div class="mt-6 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
<template v-if="computers.length > 0">
<div
v-for="computer in computers"
:key="computer.id"
class="p-4 border rounded-lg shadow hover:shadow-lg transition"
>
<h4 class="font-semibold text-lg text-gray-800">{{ computer.title }}</h4>
<p class="text-gray-600">State: {{ computer.state?.name }}</p>
<p class="text-indigo-600 font-semibold">Price: ${{ computer.sellPrice }}</p>
<p class="text-gray-500 text-sm">{{ computer.components.length }} components</p>
<Link
:href="`/computers/${computer.id}`"
class="mt-2 inline-block text-indigo-600 hover:text-indigo-700"
>
View Details
</Link>
</div>
</template>
<template v-else>
<p class="text-gray-500">No computers yet. Create one to get started!</p>
</template>
</div>
</div>
</template>
<style scoped>
.computers-container {
max-width: 1200px;
margin: 0 auto;
padding: 2rem;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 2rem;
}
.computers-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 2rem;
}
.computer-card {
border: 1px solid #e5e7eb;
border-radius: 0.5rem;
overflow: hidden;
display: flex;
flex-direction: column;
}
.card-image {
width: 100%;
height: 200px;
background-color: #f3f4f6;
display: flex;
align-items: center;
justify-content: center;
}
.card-image img {
width: 100%;
height: 100%;
object-fit: cover;
}
.no-image {
display: flex;
align-items: center;
justify-content: center;
color: #9ca3af;
}
.card-content {
padding: 1rem;
flex: 1;
display: flex;
flex-direction: column;
}
.card-content h3 {
margin: 0 0 0.5rem 0;
font-size: 1.125rem;
}
.card-content p {
color: #6b7280;
margin: 0 0 1rem 0;
flex: 1;
}
.card-meta {
display: flex;
justify-content: space-between;
margin-bottom: 1rem;
font-size: 0.875rem;
color: #6b7280;
}
.empty-state {
text-align: center;
padding: 4rem 2rem;
}
</style>
+87
View File
@@ -0,0 +1,87 @@
<script setup lang="ts">
import { Link } from '@inertiajs/vue3'
const { props } = defineProps<{ computer: any }>()
</script>
<template>
<div class="max-w-4xl mx-auto p-4 md:p-8 bg-white shadow-lg rounded-lg">
<h1 class="text-xl md:text-2xl font-semibold mb-4 md:mb-8 text-gray-800">
{{ computer.title }}
</h1>
<div class="mt-2">
<label class="block text-gray-700 text-sm font-bold mb-2">Components</label>
<div class="overflow-x-auto">
<table class="w-full border-collapse">
<thead class="bg-gray-200">
<tr>
<th class="border px-4 py-2 text-left">Name</th>
<th class="border px-4 py-2 text-left">Type</th>
<th class="border px-4 py-2 text-right">Price</th>
</tr>
</thead>
<tbody>
<tr
v-for="component in computer.components"
:key="component.id"
class="hover:bg-gray-50"
>
<td class="border px-4 py-2">{{ component.name }}</td>
<td class="border px-4 py-2">{{ component.type?.name }}</td>
<td class="border px-4 py-2 text-right">${{ component.price }}</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="mt-6 flex flex-wrap gap-4">
<div class="flex-1 min-w-[100px]">
<label class="block text-gray-700 text-sm font-bold mb-2">GPU Score</label>
<p class="w-full border-gray-200 px-3 py-2 border rounded text-gray-700">
{{ computer.gpuScore }}
</p>
</div>
<div class="flex-1 min-w-[100px]">
<label class="block text-gray-700 text-sm font-bold mb-2">CPU Score</label>
<p class="w-full border-gray-200 px-3 py-2 border rounded text-gray-700">
{{ computer.cpuScore }}
</p>
</div>
<div class="flex-1 min-w-[100px]">
<label class="block text-gray-700 text-sm font-bold mb-2">Global Score</label>
<p class="w-full border-gray-200 px-3 py-2 border rounded text-gray-700">
{{ computer.globalScore }}
</p>
</div>
</div>
<div class="mt-4 grid grid-cols-1 md:grid-cols-3 gap-4 mb-6">
<div class="pt-2">
<label class="block text-gray-700 text-sm font-bold mb-2">State</label>
<p class="w-full mt-2 border-gray-200 px-3 py-2.5 border rounded text-gray-700">
{{ computer.state.name }}
</p>
</div>
<div></div>
<div class="mt-2">
<label class="block text-gray-700 text-sm font-bold mb-2">Sell Price</label>
<p class="w-full mt-2 border-gray-200 px-3 py-2 border rounded text-gray-700">
${{ computer.sellPrice.toFixed(2) }}
</p>
</div>
</div>
<Link
:href="`/computers/${computer.id}/edit`"
type="button"
as="button"
class="w-full md:w-auto px-4 py-2 bg-indigo-600 text-white rounded hover:bg-blue-700 transition"
>
Edit
</Link>
</div>
</template>
<style scoped></style>