software.app.web
DEV
As simple as perfect

software.app.web

Soluzioni su misura, semplici e perfette. Costruite per crescere. Progettiamo e sviluppiamo applicazioni software, piattaforme web e mobile app ad alte prestazioni, scalabili, sicure, perfettamente integrate con l'ecosistema aziendale.

Sviluppo

Dalla progettazione al codice. Dal codice al prodotto.

Traduciamo requisiti, logiche di business e obiettivi operativi in software reale: web app, mobile app e soluzioni custom progettate per essere scalabili, integrate e pronte al rilascio.

Coding...
WebApp
import React, { useEffect, useMemo, useState } from 'react';
const initialForm = { title: '', owner: '', priority: 'medium' };
export default function ProjectDashboard() {
const [items, setItems] = useState([]);
const [query, setQuery] = useState('');
const [statusFilter, setStatusFilter] = useState('all');
const [loading, setLoading] = useState(true);
const [saving, setSaving] = useState(false);
const [error, setError] = useState('');
const [form, setForm] = useState(initialForm);
const [selectedId, setSelectedId] = useState(null);
useEffect(() => {
let active = true;
async function loadProjects() {
setLoading(true);
setError('');
try {
const response = await fetch('/api/projects');
if (!response.ok) throw new Error('Errore nel caricamento dati');
const data = await response.json();
if (active) {
setItems(data);
}
} catch (err) {
if (active) {
setError(err.message || 'Errore imprevisto');
}
} finally {
if (active) {
setLoading(false);
}
}
}
loadProjects();
return () => {
active = false;
};
}, []);
const filteredItems = useMemo(() => {
return items.filter((item) => {
const matchQuery =
item.title.toLowerCase().includes(query.toLowerCase()) ||
item.owner.toLowerCase().includes(query.toLowerCase());
const matchStatus =
statusFilter === 'all' ? true : item.status === statusFilter;
return matchQuery && matchStatus;
});
}, [items, query, statusFilter]);
const stats = useMemo(() => {
return {
total: items.length,
active: items.filter((item) => item.status === 'active').length,
paused: items.filter((item) => item.status === 'paused').length,
done: items.filter((item) => item.status === 'done').length,
};
}, [items]);
function updateForm(field, value) {
setForm((current) => ({
...current,
[field]: value,
}));
}
function resetForm() {
setForm(initialForm);
setSelectedId(null);
}
function selectItem(item) {
setSelectedId(item.id);
setForm({
title: item.title,
owner: item.owner,
priority: item.priority,
});
}
async function handleSubmit(event) {
event.preventDefault();
setSaving(true);
setError('');
const payload = {
title: form.title.trim(),
owner: form.owner.trim(),
priority: form.priority,
};
if (!payload.title || !payload.owner) {
setError('Titolo e responsabile sono obbligatori');
setSaving(false);
return;
}
try {
const isEditing = Boolean(selectedId);
const response = await fetch(
isEditing ? `/api/projects/${selectedId}` : '/api/projects',
{
method: isEditing ? 'PUT' : 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
}
);
if (!response.ok) throw new Error('Salvataggio non riuscito');
const savedItem = await response.json();
setItems((current) => {
if (isEditing) {
return current.map((item) =>
item.id === selectedId ? savedItem : item
);
}
return [savedItem, ...current];
});
resetForm();
} catch (err) {
setError(err.message || 'Errore durante il salvataggio');
} finally {
setSaving(false);
}
}
function toggleStatus(id) {
setItems((current) =>
current.map((item) =>
item.id === id
? {
...item,
status: item.status === 'done' ? 'active' : 'done',
}
: item
)
);
}
return (
<div className="dashboard">
<header className="toolbar">
<h1>Project Dashboard</h1>
<div className="stats">
<span>Totale: {stats.total}</span>
<span>Attivi: {stats.active}</span>
<span>In pausa: {stats.paused}</span>
<span>Completati: {stats.done}</span>
</div>
</header>
<section className="filters">
<input
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Cerca per titolo o responsabile"
/>
<select
value={statusFilter}
onChange={(e) => setStatusFilter(e.target.value)}
>
<option value="all">Tutti</option>
<option value="active">Attivi</option>
<option value="paused">In pausa</option>
<option value="done">Completati</option>
</select>
</section>
<section className="content">
<aside className="panel">
<form onSubmit={handleSubmit}>
<h2>{selectedId ? 'Modifica progetto' : 'Nuovo progetto'}</h2>
<input
value={form.title}
onChange={(e) => updateForm('title', e.target.value)}
placeholder="Titolo progetto"
/>
<input
value={form.owner}
onChange={(e) => updateForm('owner', e.target.value)}
placeholder="Responsabile"
/>
<select
value={form.priority}
onChange={(e) => updateForm('priority', e.target.value)}
>
<option value="low">Bassa</option>
<option value="medium">Media</option>
<option value="high">Alta</option>
</select>
<div className="actions">
<button type="submit" disabled={saving}>
{saving ? 'Salvataggio...' : 'Salva'}
</button>
<button type="button" onClick={resetForm}>
Reset
</button>
</div>
</form>
{error && <p className="error">{error}</p>}
</aside>
<main className="list">
{loading ? (
<p>Caricamento in corso...</p>
) : (
filteredItems.map((item) => (
<article key={item.id} className="card">
<div>
<h3>{item.title}</h3>
<p>Owner: {item.owner}</p>
<p>Priority: {item.priority}</p>
<p>Status: {item.status}</p>
</div>
<div className="cardActions">
<button onClick={() => selectItem(item)}>Modifica</button>
<button onClick={() => toggleStatus(item.id)}>
Toggle status
</button>
</div>
</article>
))
)}
</main>
</section>
</div>
);
}
import React, { useEffect, useMemo, useState } from 'react';
const initialForm = { title: '', owner: '', priority: 'medium' };
export default function ProjectDashboard() {
const [items, setItems] = useState([]);
const [query, setQuery] = useState('');
const [statusFilter, setStatusFilter] = useState('all');
const [loading, setLoading] = useState(true);
const [saving, setSaving] = useState(false);
const [error, setError] = useState('');
const [form, setForm] = useState(initialForm);
const [selectedId, setSelectedId] = useState(null);
useEffect(() => {
let active = true;
async function loadProjects() {
setLoading(true);
setError('');
try {
const response = await fetch('/api/projects');
if (!response.ok) throw new Error('Errore nel caricamento dati');
const data = await response.json();
if (active) {
setItems(data);
}
} catch (err) {
if (active) {
setError(err.message || 'Errore imprevisto');
}
} finally {
if (active) {
setLoading(false);
}
}
}
loadProjects();
return () => {
active = false;
};
}, []);
const filteredItems = useMemo(() => {
return items.filter((item) => {
const matchQuery =
item.title.toLowerCase().includes(query.toLowerCase()) ||
item.owner.toLowerCase().includes(query.toLowerCase());
const matchStatus =
statusFilter === 'all' ? true : item.status === statusFilter;
return matchQuery && matchStatus;
});
}, [items, query, statusFilter]);
const stats = useMemo(() => {
return {
total: items.length,
active: items.filter((item) => item.status === 'active').length,
paused: items.filter((item) => item.status === 'paused').length,
done: items.filter((item) => item.status === 'done').length,
};
}, [items]);
function updateForm(field, value) {
setForm((current) => ({
...current,
[field]: value,
}));
}
function resetForm() {
setForm(initialForm);
setSelectedId(null);
}
function selectItem(item) {
setSelectedId(item.id);
setForm({
title: item.title,
owner: item.owner,
priority: item.priority,
});
}
async function handleSubmit(event) {
event.preventDefault();
setSaving(true);
setError('');
const payload = {
title: form.title.trim(),
owner: form.owner.trim(),
priority: form.priority,
};
if (!payload.title || !payload.owner) {
setError('Titolo e responsabile sono obbligatori');
setSaving(false);
return;
}
try {
const isEditing = Boolean(selectedId);
const response = await fetch(
isEditing ? `/api/projects/${selectedId}` : '/api/projects',
{
method: isEditing ? 'PUT' : 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
}
);
if (!response.ok) throw new Error('Salvataggio non riuscito');
const savedItem = await response.json();
setItems((current) => {
if (isEditing) {
return current.map((item) =>
item.id === selectedId ? savedItem : item
);
}
return [savedItem, ...current];
});
resetForm();
} catch (err) {
setError(err.message || 'Errore durante il salvataggio');
} finally {
setSaving(false);
}
}
function toggleStatus(id) {
setItems((current) =>
current.map((item) =>
item.id === id
? {
...item,
status: item.status === 'done' ? 'active' : 'done',
}
: item
)
);
}
return (
<div className="dashboard">
<header className="toolbar">
<h1>Project Dashboard</h1>
<div className="stats">
<span>Totale: {stats.total}</span>
<span>Attivi: {stats.active}</span>
<span>In pausa: {stats.paused}</span>
<span>Completati: {stats.done}</span>
</div>
</header>
<section className="filters">
<input
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Cerca per titolo o responsabile"
/>
<select
value={statusFilter}
onChange={(e) => setStatusFilter(e.target.value)}
>
<option value="all">Tutti</option>
<option value="active">Attivi</option>
<option value="paused">In pausa</option>
<option value="done">Completati</option>
</select>
</section>
<section className="content">
<aside className="panel">
<form onSubmit={handleSubmit}>
<h2>{selectedId ? 'Modifica progetto' : 'Nuovo progetto'}</h2>
<input
value={form.title}
onChange={(e) => updateForm('title', e.target.value)}
placeholder="Titolo progetto"
/>
<input
value={form.owner}
onChange={(e) => updateForm('owner', e.target.value)}
placeholder="Responsabile"
/>
<select
value={form.priority}
onChange={(e) => updateForm('priority', e.target.value)}
>
<option value="low">Bassa</option>
<option value="medium">Media</option>
<option value="high">Alta</option>
</select>
<div className="actions">
<button type="submit" disabled={saving}>
{saving ? 'Salvataggio...' : 'Salva'}
</button>
<button type="button" onClick={resetForm}>
Reset
</button>
</div>
</form>
{error && <p className="error">{error}</p>}
</aside>
<main className="list">
{loading ? (
<p>Caricamento in corso...</p>
) : (
filteredItems.map((item) => (
<article key={item.id} className="card">
<div>
<h3>{item.title}</h3>
<p>Owner: {item.owner}</p>
<p>Priority: {item.priority}</p>
<p>Status: {item.status}</p>
</div>
<div className="cardActions">
<button onClick={() => selectItem(item)}>Modifica</button>
<button onClick={() => toggleStatus(item.id)}>
Toggle status
</button>
</div>
</article>
))
)}
</main>
</section>
</div>
);
}
Mobile
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import {
ActivityIndicator,
FlatList,
Pressable,
SafeAreaView,
StyleSheet,
Text,
TextInput,
View,
} from 'react-native';
const API_URL = 'https://example.com/api/tasks';
export default function TaskMobileScreen() {
const [tasks, setTasks] = useState([]);
const [query, setQuery] = useState('');
const [tab, setTab] = useState('all');
const [loading, setLoading] = useState(true);
const [refreshing, setRefreshing] = useState(false);
const [error, setError] = useState('');
const fetchTasks = useCallback(async (mode = 'initial') => {
if (mode === 'initial') setLoading(true);
if (mode === 'refresh') setRefreshing(true);
setError('');
try {
const response = await fetch(API_URL);
if (!response.ok) throw new Error('Impossibile caricare i task');
const data = await response.json();
setTasks(data);
} catch (err) {
setError(err.message || 'Errore di rete');
} finally {
if (mode === 'initial') setLoading(false);
if (mode === 'refresh') setRefreshing(false);
}
}, []);
useEffect(() => {
fetchTasks();
}, [fetchTasks]);
const filteredTasks = useMemo(() => {
return tasks.filter((task) => {
const textMatch =
task.title.toLowerCase().includes(query.toLowerCase()) ||
task.client.toLowerCase().includes(query.toLowerCase());
const tabMatch = tab === 'all' ? true : task.status === tab;
return textMatch && tabMatch;
});
}, [tasks, query, tab]);
const summary = useMemo(() => {
return {
total: tasks.length,
todo: tasks.filter((task) => task.status === 'todo').length,
doing: tasks.filter((task) => task.status === 'doing').length,
done: tasks.filter((task) => task.status === 'done').length,
};
}, [tasks]);
function toggleTask(id) {
setTasks((current) =>
current.map((task) =>
task.id === id
? {
...task,
status: task.status === 'done' ? 'doing' : 'done',
}
: task
)
);
}
function renderTab(label, value) {
const active = tab === value;
return (
<Pressable
key={value}
style={[styles.tabButton, active && styles.tabButtonActive]}
onPress={() => setTab(value)}
>
<Text style={[styles.tabText, active && styles.tabTextActive]}>
{label}
</Text>
</Pressable>
);
}
function renderItem({ item }) {
return (
<View style={styles.card}>
<View style={styles.cardTop}>
<View>
<Text style={styles.cardTitle}>{item.title}</Text>
<Text style={styles.cardClient}>{item.client}</Text>
</View>
<Text style={styles.cardStatus}>{item.status}</Text>
</View>
<Text style={styles.cardMeta}>Priority: {item.priority}</Text>
<Text style={styles.cardMeta}>Deadline: {item.deadline}</Text>
<Pressable
style={styles.primaryButton}
onPress={() => toggleTask(item.id)}
>
<Text style={styles.primaryButtonText}>Aggiorna stato</Text>
</Pressable>
</View>
);
}
if (loading) {
return (
<SafeAreaView style={styles.centered}>
<ActivityIndicator size="large" />
<Text style={styles.loadingText}>Caricamento task...</Text>
</SafeAreaView>
);
}
return (
<SafeAreaView style={styles.container}>
<Text style={styles.title}>Mobile Task Board</Text>
<View style={styles.summaryRow}>
<Text style={styles.summaryText}>Totale: {summary.total}</Text>
<Text style={styles.summaryText}>To do: {summary.todo}</Text>
<Text style={styles.summaryText}>Doing: {summary.doing}</Text>
<Text style={styles.summaryText}>Done: {summary.done}</Text>
</View>
<TextInput
value={query}
onChangeText={setQuery}
placeholder="Cerca task o cliente"
style={styles.searchInput}
/>
<View style={styles.tabsRow}>
{renderTab('Tutti', 'all')}
{renderTab('To do', 'todo')}
{renderTab('Doing', 'doing')}
{renderTab('Done', 'done')}
</View>
{error ? <Text style={styles.errorText}>{error}</Text> : null}
<FlatList
data={filteredTasks}
keyExtractor={(item) => String(item.id)}
renderItem={renderItem}
refreshing={refreshing}
onRefresh={() => fetchTasks('refresh')}
contentContainerStyle={styles.listContent}
ListEmptyComponent={
<Text style={styles.emptyText}>Nessun task trovato</Text>
}
/>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: { flex: 1, padding: 16 },
centered: { flex: 1, justifyContent: 'center', alignItems: 'center' },
loadingText: { marginTop: 12, fontSize: 16 },
title: { fontSize: 28, fontWeight: '700', marginBottom: 16 },
summaryRow: { flexDirection: 'row', flexWrap: 'wrap', marginBottom: 12 },
summaryText: { marginRight: 12, marginBottom: 8, fontSize: 14 },
searchInput: {
borderWidth: 1,
borderColor: '#D0D5DD',
borderRadius: 10,
paddingHorizontal: 12,
paddingVertical: 10,
marginBottom: 12,
},
tabsRow: { flexDirection: 'row', marginBottom: 12 },
tabButton: {
paddingVertical: 8,
paddingHorizontal: 12,
borderRadius: 999,
marginRight: 8,
borderWidth: 1,
borderColor: '#D0D5DD',
},
tabButtonActive: { backgroundColor: '#111827', borderColor: '#111827' },
tabText: { fontSize: 14, color: '#111827' },
tabTextActive: { color: '#FFFFFF' },
errorText: { color: '#B42318', marginBottom: 12 },
listContent: { paddingBottom: 24 },
emptyText: { textAlign: 'center', marginTop: 40, fontSize: 15 },
card: {
borderWidth: 1,
borderColor: '#EAECF0',
borderRadius: 14,
padding: 14,
marginBottom: 12,
},
cardTop: {
flexDirection: 'row',
justifyContent: 'space-between',
marginBottom: 8,
},
cardTitle: { fontSize: 18, fontWeight: '600' },
cardClient: { fontSize: 14, marginTop: 2 },
cardStatus: { fontSize: 13, fontWeight: '700' },
cardMeta: { fontSize: 14, marginBottom: 4 },
primaryButton: {
marginTop: 10,
paddingVertical: 10,
borderRadius: 10,
alignItems: 'center',
backgroundColor: '#111827',
},
primaryButtonText: { color: '#FFFFFF', fontWeight: '600' },
});
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import {
ActivityIndicator,
FlatList,
Pressable,
SafeAreaView,
StyleSheet,
Text,
TextInput,
View,
} from 'react-native';
const API_URL = 'https://example.com/api/tasks';
export default function TaskMobileScreen() {
const [tasks, setTasks] = useState([]);
const [query, setQuery] = useState('');
const [tab, setTab] = useState('all');
const [loading, setLoading] = useState(true);
const [refreshing, setRefreshing] = useState(false);
const [error, setError] = useState('');
const fetchTasks = useCallback(async (mode = 'initial') => {
if (mode === 'initial') setLoading(true);
if (mode === 'refresh') setRefreshing(true);
setError('');
try {
const response = await fetch(API_URL);
if (!response.ok) throw new Error('Impossibile caricare i task');
const data = await response.json();
setTasks(data);
} catch (err) {
setError(err.message || 'Errore di rete');
} finally {
if (mode === 'initial') setLoading(false);
if (mode === 'refresh') setRefreshing(false);
}
}, []);
useEffect(() => {
fetchTasks();
}, [fetchTasks]);
const filteredTasks = useMemo(() => {
return tasks.filter((task) => {
const textMatch =
task.title.toLowerCase().includes(query.toLowerCase()) ||
task.client.toLowerCase().includes(query.toLowerCase());
const tabMatch = tab === 'all' ? true : task.status === tab;
return textMatch && tabMatch;
});
}, [tasks, query, tab]);
const summary = useMemo(() => {
return {
total: tasks.length,
todo: tasks.filter((task) => task.status === 'todo').length,
doing: tasks.filter((task) => task.status === 'doing').length,
done: tasks.filter((task) => task.status === 'done').length,
};
}, [tasks]);
function toggleTask(id) {
setTasks((current) =>
current.map((task) =>
task.id === id
? {
...task,
status: task.status === 'done' ? 'doing' : 'done',
}
: task
)
);
}
function renderTab(label, value) {
const active = tab === value;
return (
<Pressable
key={value}
style={[styles.tabButton, active && styles.tabButtonActive]}
onPress={() => setTab(value)}
>
<Text style={[styles.tabText, active && styles.tabTextActive]}>
{label}
</Text>
</Pressable>
);
}
function renderItem({ item }) {
return (
<View style={styles.card}>
<View style={styles.cardTop}>
<View>
<Text style={styles.cardTitle}>{item.title}</Text>
<Text style={styles.cardClient}>{item.client}</Text>
</View>
<Text style={styles.cardStatus}>{item.status}</Text>
</View>
<Text style={styles.cardMeta}>Priority: {item.priority}</Text>
<Text style={styles.cardMeta}>Deadline: {item.deadline}</Text>
<Pressable
style={styles.primaryButton}
onPress={() => toggleTask(item.id)}
>
<Text style={styles.primaryButtonText}>Aggiorna stato</Text>
</Pressable>
</View>
);
}
if (loading) {
return (
<SafeAreaView style={styles.centered}>
<ActivityIndicator size="large" />
<Text style={styles.loadingText}>Caricamento task...</Text>
</SafeAreaView>
);
}
return (
<SafeAreaView style={styles.container}>
<Text style={styles.title}>Mobile Task Board</Text>
<View style={styles.summaryRow}>
<Text style={styles.summaryText}>Totale: {summary.total}</Text>
<Text style={styles.summaryText}>To do: {summary.todo}</Text>
<Text style={styles.summaryText}>Doing: {summary.doing}</Text>
<Text style={styles.summaryText}>Done: {summary.done}</Text>
</View>
<TextInput
value={query}
onChangeText={setQuery}
placeholder="Cerca task o cliente"
style={styles.searchInput}
/>
<View style={styles.tabsRow}>
{renderTab('Tutti', 'all')}
{renderTab('To do', 'todo')}
{renderTab('Doing', 'doing')}
{renderTab('Done', 'done')}
</View>
{error ? <Text style={styles.errorText}>{error}</Text> : null}
<FlatList
data={filteredTasks}
keyExtractor={(item) => String(item.id)}
renderItem={renderItem}
refreshing={refreshing}
onRefresh={() => fetchTasks('refresh')}
contentContainerStyle={styles.listContent}
ListEmptyComponent={
<Text style={styles.emptyText}>Nessun task trovato</Text>
}
/>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: { flex: 1, padding: 16 },
centered: { flex: 1, justifyContent: 'center', alignItems: 'center' },
loadingText: { marginTop: 12, fontSize: 16 },
title: { fontSize: 28, fontWeight: '700', marginBottom: 16 },
summaryRow: { flexDirection: 'row', flexWrap: 'wrap', marginBottom: 12 },
summaryText: { marginRight: 12, marginBottom: 8, fontSize: 14 },
searchInput: {
borderWidth: 1,
borderColor: '#D0D5DD',
borderRadius: 10,
paddingHorizontal: 12,
paddingVertical: 10,
marginBottom: 12,
},
tabsRow: { flexDirection: 'row', marginBottom: 12 },
tabButton: {
paddingVertical: 8,
paddingHorizontal: 12,
borderRadius: 999,
marginRight: 8,
borderWidth: 1,
borderColor: '#D0D5DD',
},
tabButtonActive: { backgroundColor: '#111827', borderColor: '#111827' },
tabText: { fontSize: 14, color: '#111827' },
tabTextActive: { color: '#FFFFFF' },
errorText: { color: '#B42318', marginBottom: 12 },
listContent: { paddingBottom: 24 },
emptyText: { textAlign: 'center', marginTop: 40, fontSize: 15 },
card: {
borderWidth: 1,
borderColor: '#EAECF0',
borderRadius: 14,
padding: 14,
marginBottom: 12,
},
cardTop: {
flexDirection: 'row',
justifyContent: 'space-between',
marginBottom: 8,
},
cardTitle: { fontSize: 18, fontWeight: '600' },
cardClient: { fontSize: 14, marginTop: 2 },
cardStatus: { fontSize: 13, fontWeight: '700' },
cardMeta: { fontSize: 14, marginBottom: 4 },
primaryButton: {
marginTop: 10,
paddingVertical: 10,
borderRadius: 10,
alignItems: 'center',
backgroundColor: '#111827',
},
primaryButtonText: { color: '#FFFFFF', fontWeight: '600' },
});
DEV-02
Web. Mobile. Software. Performance.

Soluzioni digitali costruite per crescere

Ogni progetto è sviluppato con un approccio orientato a qualità, manutenibilità e integrazione. Performance, sicurezza e continuità evolutiva non sono elementi accessori, ma requisiti strutturali di ogni soluzione.

Coding...
Python
from datetime import date
from typing import List, Literal, Optional
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, Field
app = FastAPI(title="Project Delivery API", version="1.0.0")
class TaskBase(BaseModel):
title: str = Field(..., min_length=3, max_length=120)
client: str = Field(..., min_length=2, max_length=80)
priority: Literal["low", "medium", "high"]
status: Literal["todo", "doing", "done"] = "todo"
due_date: Optional[date] = None
class TaskCreate(TaskBase):
pass
class TaskUpdate(BaseModel):
title: Optional[str] = Field(None, min_length=3, max_length=120)
client: Optional[str] = Field(None, min_length=2, max_length=80)
priority: Optional[Literal["low", "medium", "high"]] = None
status: Optional[Literal["todo", "doing", "done"]] = None
due_date: Optional[date] = None
class Task(TaskBase):
id: int
fake_db: List[Task] = [
Task(
id=1,
title="Customer portal redesign",
client="Alfa Srl",
priority="high",
status="doing",
due_date=date(2026, 4, 20),
),
Task(
id=2,
title="Mobile onboarding flow",
client="Beta Group",
priority="medium",
status="todo",
due_date=date(2026, 4, 28),
),
Task(
id=3,
title="Analytics dashboard",
client="Gamma Tech",
priority="high",
status="done",
due_date=date(2026, 4, 10),
),
]
def get_next_id() -> int:
if not fake_db:
return 1
return max(task.id for task in fake_db) + 1
def find_task(task_id: int) -> Task:
for task in fake_db:
if task.id == task_id:
return task
raise HTTPException(status_code=404, detail="Task non trovato")
@app.get("/health")
def healthcheck():
return {"status": "ok", "service": "project-delivery-api"}
@app.get("/tasks", response_model=List[Task])
def list_tasks(
status: Optional[Literal["todo", "doing", "done"]] = None,
priority: Optional[Literal["low", "medium", "high"]] = None,
q: Optional[str] = None,
):
results = fake_db
if status:
results = [task for task in results if task.status == status]
if priority:
results = [task for task in results if task.priority == priority]
if q:
q_lower = q.lower()
results = [
task
for task in results
if q_lower in task.title.lower() or q_lower in task.client.lower()
]
return results
@app.get("/tasks/{task_id}", response_model=Task)
def get_task(task_id: int):
return find_task(task_id)
@app.post("/tasks", response_model=Task, status_code=201)
def create_task(payload: TaskCreate):
new_task = Task(id=get_next_id(), **payload.model_dump())
fake_db.append(new_task)
return new_task
@app.put("/tasks/{task_id}", response_model=Task)
def update_task(task_id: int, payload: TaskUpdate):
task = find_task(task_id)
update_data = payload.model_dump(exclude_unset=True)
updated_task = task.model_copy(update=update_data)
index = next(i for i, item in enumerate(fake_db) if item.id == task_id)
fake_db[index] = updated_task
return updated_task
@app.delete("/tasks/{task_id}", status_code=204)
def delete_task(task_id: int):
task = find_task(task_id)
fake_db.remove(task)
return None
@app.get("/metrics/summary")
def get_summary():
total = len(fake_db)
todo = len([task for task in fake_db if task.status == "todo"])
doing = len([task for task in fake_db if task.status == "doing"])
done = len([task for task in fake_db if task.status == "done"])
high_priority = len([task for task in fake_db if task.priority == "high"])
return {
"total": total,
"todo": todo,
"doing": doing,
"done": done,
"high_priority": high_priority,
}
@app.post("/tasks/{task_id}/complete", response_model=Task)
def complete_task(task_id: int):
task = find_task(task_id)
completed_task = task.model_copy(update={"status": "done"})
index = next(i for i, item in enumerate(fake_db) if item.id == task_id)
fake_db[index] = completed_task
return completed_task
@app.get("/clients")
def list_clients():
clients = sorted({task.client for task in fake_db})
return {"clients": clients}
@app.get("/priorities/distribution")
def priority_distribution():
return {
"low": len([task for task in fake_db if task.priority == "low"]),
"medium": len([task for task in fake_db if task.priority == "medium"]),
"high": len([task for task in fake_db if task.priority == "high"]),
}
from datetime import date
from typing import List, Literal, Optional
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, Field
app = FastAPI(title="Project Delivery API", version="1.0.0")
class TaskBase(BaseModel):
title: str = Field(..., min_length=3, max_length=120)
client: str = Field(..., min_length=2, max_length=80)
priority: Literal["low", "medium", "high"]
status: Literal["todo", "doing", "done"] = "todo"
due_date: Optional[date] = None
class TaskCreate(TaskBase):
pass
class TaskUpdate(BaseModel):
title: Optional[str] = Field(None, min_length=3, max_length=120)
client: Optional[str] = Field(None, min_length=2, max_length=80)
priority: Optional[Literal["low", "medium", "high"]] = None
status: Optional[Literal["todo", "doing", "done"]] = None
due_date: Optional[date] = None
class Task(TaskBase):
id: int
fake_db: List[Task] = [
Task(
id=1,
title="Customer portal redesign",
client="Alfa Srl",
priority="high",
status="doing",
due_date=date(2026, 4, 20),
),
Task(
id=2,
title="Mobile onboarding flow",
client="Beta Group",
priority="medium",
status="todo",
due_date=date(2026, 4, 28),
),
Task(
id=3,
title="Analytics dashboard",
client="Gamma Tech",
priority="high",
status="done",
due_date=date(2026, 4, 10),
),
]
def get_next_id() -> int:
if not fake_db:
return 1
return max(task.id for task in fake_db) + 1
def find_task(task_id: int) -> Task:
for task in fake_db:
if task.id == task_id:
return task
raise HTTPException(status_code=404, detail="Task non trovato")
@app.get("/health")
def healthcheck():
return {"status": "ok", "service": "project-delivery-api"}
@app.get("/tasks", response_model=List[Task])
def list_tasks(
status: Optional[Literal["todo", "doing", "done"]] = None,
priority: Optional[Literal["low", "medium", "high"]] = None,
q: Optional[str] = None,
):
results = fake_db
if status:
results = [task for task in results if task.status == status]
if priority:
results = [task for task in results if task.priority == priority]
if q:
q_lower = q.lower()
results = [
task
for task in results
if q_lower in task.title.lower() or q_lower in task.client.lower()
]
return results
@app.get("/tasks/{task_id}", response_model=Task)
def get_task(task_id: int):
return find_task(task_id)
@app.post("/tasks", response_model=Task, status_code=201)
def create_task(payload: TaskCreate):
new_task = Task(id=get_next_id(), **payload.model_dump())
fake_db.append(new_task)
return new_task
@app.put("/tasks/{task_id}", response_model=Task)
def update_task(task_id: int, payload: TaskUpdate):
task = find_task(task_id)
update_data = payload.model_dump(exclude_unset=True)
updated_task = task.model_copy(update=update_data)
index = next(i for i, item in enumerate(fake_db) if item.id == task_id)
fake_db[index] = updated_task
return updated_task
@app.delete("/tasks/{task_id}", status_code=204)
def delete_task(task_id: int):
task = find_task(task_id)
fake_db.remove(task)
return None
@app.get("/metrics/summary")
def get_summary():
total = len(fake_db)
todo = len([task for task in fake_db if task.status == "todo"])
doing = len([task for task in fake_db if task.status == "doing"])
done = len([task for task in fake_db if task.status == "done"])
high_priority = len([task for task in fake_db if task.priority == "high"])
return {
"total": total,
"todo": todo,
"doing": doing,
"done": done,
"high_priority": high_priority,
}
@app.post("/tasks/{task_id}/complete", response_model=Task)
def complete_task(task_id: int):
task = find_task(task_id)
completed_task = task.model_copy(update={"status": "done"})
index = next(i for i, item in enumerate(fake_db) if item.id == task_id)
fake_db[index] = completed_task
return completed_task
@app.get("/clients")
def list_clients():
clients = sorted({task.client for task in fake_db})
return {"clients": clients}
@app.get("/priorities/distribution")
def priority_distribution():
return {
"low": len([task for task in fake_db if task.priority == "low"]),
"medium": len([task for task in fake_db if task.priority == "medium"]),
"high": len([task for task in fake_db if task.priority == "high"]),
}
Sviluppo

Dall’idea al rilascio

Progettiamo soluzioni digitali su misura, costruite per adattarsi ai processi aziendali e non viceversa. Utilizziamo architetture moderne e modulari per garantire scalabilità, sicurezza e performance.

Architettura

Architetture API-first, scalabili e integrate

Costruiamo basi solide per prodotti digitali evolutivi, sicuri e pronti a crescere.

Output

Web app, mobile app, software custom

Realizziamo soluzioni su misura progettate per adattarsi ai flussi e agli obiettivi aziendali.

Metodo

Agile development, rilascio continuo

Riduciamo il time-to-market e miglioriamo la qualità del prodotto con un approccio iterativo.

Esperienza

Interfacce chiare, esperienza fluida

Progettiamo esperienze fluide, essenziali e coerenti con i processi reali degli utenti.

Integrazione

Tecnologia connessa all’ecosistema aziendale

Ogni soluzione dialoga con sistemi esistenti, dati e servizi già presenti in azienda.

Next step

Parliamo del software che vuoi costruire

Contattaci per progettare software, web app o mobile app realmente allineati al tuo business.