Initial commit

This commit is contained in:
Zhongwei Li
2025-11-29 18:50:06 +08:00
commit 5e3ca965d9
68 changed files with 11257 additions and 0 deletions

View File

@@ -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;

View File

@@ -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>

View File

@@ -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>