Initial commit
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
// Disable SSR for this app (client-side only with Odoo backend)
|
||||
export const ssr = false;
|
||||
export const csr = true;
|
||||
export const prerender = true;
|
||||
@@ -0,0 +1,24 @@
|
||||
<script>
|
||||
let { children } = $props();
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>{{PROJECT_NAME}} - {{MODEL_DISPLAY_NAME}} Manager</title>
|
||||
<meta name="description" content="Offline-first {{MODEL_DISPLAY_NAME}} management app with Odoo integration" />
|
||||
</svelte:head>
|
||||
|
||||
{@render children?.()}
|
||||
|
||||
<style>
|
||||
:global(body) {
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen-Sans, Ubuntu,
|
||||
Cantarell, 'Helvetica Neue', sans-serif;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
:global(*) {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,271 @@
|
||||
<script>
|
||||
import { {{MODEL_NAME}}Cache } from '$lib/stores/cache';
|
||||
import { onMount, onDestroy } from 'svelte';
|
||||
|
||||
let name = $state('');
|
||||
let loading = $state(false);
|
||||
let message = $state('');
|
||||
let isOffline = $state(!navigator.onLine);
|
||||
|
||||
// Listen for online/offline events
|
||||
if (typeof window !== 'undefined') {
|
||||
window.addEventListener('online', () => { isOffline = false; });
|
||||
window.addEventListener('offline', () => { isOffline = true; });
|
||||
}
|
||||
|
||||
onMount(async () => {
|
||||
await {{MODEL_NAME}}Cache.initialize();
|
||||
});
|
||||
|
||||
onDestroy(() => {
|
||||
{{MODEL_NAME}}Cache.destroy();
|
||||
});
|
||||
|
||||
async function handleSubmit() {
|
||||
if (!name.trim()) {
|
||||
message = '⚠️ Please fill in the name field';
|
||||
return;
|
||||
}
|
||||
|
||||
loading = true;
|
||||
message = '';
|
||||
|
||||
try {
|
||||
const payload = {
|
||||
x_name: name
|
||||
// Add more fields as needed
|
||||
};
|
||||
|
||||
await {{MODEL_NAME}}Cache.createRecord(payload);
|
||||
|
||||
if (navigator.onLine) {
|
||||
message = '✅ {{MODEL_DISPLAY_NAME}} added successfully!';
|
||||
} else {
|
||||
message = '✅ {{MODEL_DISPLAY_NAME}} saved locally! Will sync when online.';
|
||||
}
|
||||
|
||||
// Reset form
|
||||
name = '';
|
||||
} catch (error) {
|
||||
message = `❌ Error: ${error.message}`;
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
function handleRefresh() {
|
||||
{{MODEL_NAME}}Cache.forceRefresh();
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Add {{MODEL_DISPLAY_NAME}} - {{PROJECT_NAME}}</title>
|
||||
</svelte:head>
|
||||
|
||||
<div class="container">
|
||||
<h1>📋 {{PROJECT_NAME}}</h1>
|
||||
|
||||
<!-- Offline Indicator -->
|
||||
{#if isOffline}
|
||||
<div class="offline-banner">
|
||||
📡 Offline Mode - Data will be synced when you're back online
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<nav>
|
||||
<a href="/" class="active">Add {{MODEL_DISPLAY_NAME}}</a>
|
||||
<a href="/list">View All</a>
|
||||
</nav>
|
||||
|
||||
<form onsubmit={(e) => { e.preventDefault(); handleSubmit(); }}>
|
||||
<div class="form-group">
|
||||
<label for="name">Name</label>
|
||||
<input
|
||||
type="text"
|
||||
id="name"
|
||||
bind:value={name}
|
||||
placeholder="Enter {{MODEL_DISPLAY_NAME}} name"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Add more form fields based on your Odoo model -->
|
||||
|
||||
{#if message}
|
||||
<div class="message" class:error={message.includes('❌')}>{message}</div>
|
||||
{/if}
|
||||
|
||||
<div class="button-group">
|
||||
<button type="submit" disabled={loading}>
|
||||
{loading ? '⏳ Adding...' : '➕ Add {{MODEL_DISPLAY_NAME}}'}
|
||||
</button>
|
||||
{#if !isOffline}
|
||||
<button type="button" class="refresh-btn" onclick={handleRefresh}>
|
||||
🔄 Refresh Data
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.container {
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.container {
|
||||
padding: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: white;
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
font-size: 2.5em;
|
||||
}
|
||||
|
||||
nav {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-bottom: 30px;
|
||||
background: white;
|
||||
border-radius: 10px;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
nav a {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
padding: 12px;
|
||||
text-decoration: none;
|
||||
color: #667eea;
|
||||
border-radius: 8px;
|
||||
font-weight: 600;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
nav a.active {
|
||||
background: #667eea;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.offline-banner {
|
||||
background: #e3f2fd;
|
||||
color: #1565c0;
|
||||
padding: 12px 20px;
|
||||
border-radius: 10px;
|
||||
margin-bottom: 20px;
|
||||
text-align: center;
|
||||
font-weight: 600;
|
||||
border: 2px solid #64b5f6;
|
||||
animation: pulse 2s infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.8; }
|
||||
}
|
||||
|
||||
form {
|
||||
background: white;
|
||||
padding: 24px;
|
||||
border-radius: 15px;
|
||||
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
form {
|
||||
padding: 16px;
|
||||
border-radius: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
input,
|
||||
select,
|
||||
textarea {
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
border: 2px solid #e0e0e0;
|
||||
border-radius: 8px;
|
||||
font-size: 16px;
|
||||
transition: border-color 0.3s;
|
||||
}
|
||||
|
||||
input:focus,
|
||||
select:focus,
|
||||
textarea:focus {
|
||||
outline: none;
|
||||
border-color: #667eea;
|
||||
}
|
||||
|
||||
.button-group {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
button {
|
||||
flex: 1;
|
||||
min-width: 150px;
|
||||
padding: 15px;
|
||||
background: #667eea;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
button:hover:not(:disabled) {
|
||||
background: #5568d3;
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 5px 20px rgba(102, 126, 234, 0.4);
|
||||
}
|
||||
|
||||
button:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.refresh-btn {
|
||||
background: #f0f0f0;
|
||||
color: #667eea;
|
||||
flex: 0 1 auto;
|
||||
}
|
||||
|
||||
.refresh-btn:hover:not(:disabled) {
|
||||
background: #e0e0e0;
|
||||
color: #5568d3;
|
||||
}
|
||||
|
||||
.message {
|
||||
padding: 12px;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 15px;
|
||||
background: #d4edda;
|
||||
color: #155724;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.message.error {
|
||||
background: #f8d7da;
|
||||
color: #721c24;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user