Files
harbour-henflow/qml/pages/MainPage.qml
T
kaneda 248e0db899 schermate di configurazione complete: razze disponibili, mio pollaio, registro galline, clienti, backup/ripristino nel formato dell'app Android, lingua a runtime, introduzione e informazioni
- datastore: export/import JSON con le chiavi camelCase dell'app Android (breedSettings/reservations/customers), scorte e ultimo aggiornamento preservati al ripristino
- settings: flag 'introduzione vista'
- desktop: permesso Documents (dove finisce il backup)
- nuovi controlli: tools/check_qml.py (chiavi di traduzione, simboli JS, pagine, componenti) e qmllint nella suite
- versione 0.2.0
2026-10-09 09:21:47 +02:00

361 lines
12 KiB
QML

import QtQuick 2.2
import Sailfish.Silica 1.0
import "../js/henflow.js" as HF
import "../js/i18n.js" as I18N
import "../js/catalog.js" as Catalog
// Home: produzione stimata del giorno e coda degli ordini.
// Porting di HomePage + _buildOrdersListTab dell'app Flutter.
Page {
id: page
allowedOrientations: Orientation.All
property var breeds: []
property var reservations: []
property var stock: ({})
property var productionRows: []
property real totalProduction: 0
property string hintMsg: ""
property string banner: ""
property string lang: "en"
property bool ready: false
function tr(key, arg) { return I18N.t(key, arg, page.lang) }
function breedLabel(breedId) {
var c = Catalog.breedFromId(breedId)
return c ? page.tr(c.name) : breedId
}
function reload() {
page.lang = I18N.langFor(appSettings.language, Qt.locale().name)
page.breeds = appData.breedSettings()
page.reservations = appData.reservations()
page.stock = appData.warehouseStock()
page.accrue()
page.totalProduction = HF.totalDailyProduction(page.breeds, Catalog.HF_BREEDS)
page.productionRows = HF.productionDetail(page.breeds, Catalog.HF_BREEDS)
page.rebuildList()
}
// Accrual del magazzino all'apertura (come _loadData in main.dart): i giorni
// passati dall'ultimo aggiornamento aggiungono la produzione giornaliera;
// poi le date di consegna vengono ricalcolate sulla coda FIFO.
function accrue() {
var today = HF.todayMidnight()
var last = appData.lastProductionUpdate()
var since = last ? HF.parseIso(last) : HF.addDays(today, -1)
var days = HF.dayDiff(since, today)
if (days > 0) {
page.stock = HF.accrueStock(page.stock, page.breeds, Catalog.HF_BREEDS, days)
appData.saveWarehouseStock(page.stock)
}
page.reservations = HF.refreshDeliveryDates(page.reservations, page.stock,
page.breeds, Catalog.HF_BREEDS, today)
appData.saveReservations(page.reservations)
appData.saveLastProductionUpdate(HF.isoDay(today))
}
function rebuildList() {
orderModel.clear()
var today = HF.todayMidnight()
for (var i = 0; i < page.reservations.length; i++) {
var r = page.reservations[i]
var days = HF.dayDiff(today, HF.parseIso(r.deliveryDate))
var status
if (r.isDelivered) status = page.tr("ready_delivery")
else if (days < 0) status = page.tr("expired")
else if (days === 0) status = page.tr("today")
else if (days === 1) status = page.tr("in_day")
else status = page.tr("in_days", days)
orderModel.append({
"customer": r.customerName,
"breed": page.breedLabel(r.breedId),
"eggs": r.eggsRequested,
"delivery": HF.formatDay(r.deliveryDate),
"status": status,
"late": (!r.isDelivered && days < 0),
"delivered": r.isDelivered
})
}
page.hintMsg = page.reservations.length === 0
? page.tr("no_reservations") + "\n" + page.tr("use_button")
: ""
}
function showBanner(text) {
page.banner = text
bannerTimer.restart()
}
function openOrder(index) {
if (page.breeds.length === 0) {
page.showBanner(page.tr("config_breed_first"))
return
}
var reservation = null
if (index >= 0 && index < page.reservations.length) {
reservation = page.reservations[index]
} else if (page.totalProduction <= 0) {
page.showBanner(page.tr("need_hen_error"))
return
}
pageStack.push(Qt.resolvedUrl("OrderPage.qml"),
{ "reservation": reservation,
"onSaved": function () { page.reload() } })
}
function toggleDelivered(index) {
var list = page.reservations.slice()
if (index < 0 || index >= list.length)
return
list[index].isDelivered = !list[index].isDelivered
page.reservations = list
appData.saveReservations(list)
page.rebuildList()
}
function deleteOrder(index, item) {
var list = page.reservations.slice()
if (index < 0 || index >= list.length)
return
list.splice(index, 1)
page.reservations = list
appData.saveReservations(list)
page.rebuildList()
remorse.execute(item, page.tr("delete"), function () {}, 3000)
}
ListModel { id: orderModel }
RemorseItem { id: remorse }
Timer {
id: bannerTimer
interval: 3000
onTriggered: page.banner = ""
}
SilicaListView {
id: listView
anchors.fill: parent
model: orderModel
header: Column {
width: listView.width
PageHeader {
title: page.tr("app_title")
}
// card della produzione stimata
Rectangle {
x: Theme.horizontalPageMargin
width: listView.width - 2 * Theme.horizontalPageMargin
height: productionColumn.height + 2 * Theme.paddingMedium
radius: Theme.paddingSmall
color: Theme.rgba(Theme.highlightBackgroundColor, 0.12)
Column {
id: productionColumn
x: Theme.paddingMedium
y: Theme.paddingMedium
width: parent.width - 2 * Theme.paddingMedium
spacing: Theme.paddingSmall
Label {
text: page.tr("daily_prod_est")
font.pixelSize: Theme.fontSizeExtraSmall
color: Theme.secondaryColor
}
Label {
text: page.totalProduction.toFixed(1) + " " + page.tr("eggs_per_day")
font.pixelSize: Theme.fontSizeHuge
color: Theme.highlightColor
}
Repeater {
model: page.productionRows.length
delegate: Label {
width: parent.width
truncationMode: TruncationMode.Fade
font.pixelSize: Theme.fontSizeExtraSmall
color: Theme.secondaryColor
text: page.breedLabel(page.productionRows[index].breedId)
+ " (" + page.productionRows[index].hens + " "
+ page.tr("hens") + ") "
+ page.productionRows[index].daily.toFixed(1) + " "
+ page.tr("eggs_per_g")
}
}
Label {
visible: page.productionRows.length === 0
width: parent.width
wrapMode: Text.WordWrap
font.pixelSize: Theme.fontSizeExtraSmall
color: Theme.secondaryColor
text: page.tr("no_breeds")
}
}
}
Label {
x: Theme.horizontalPageMargin
text: page.tr("reservations")
font.pixelSize: Theme.fontSizeSmall
color: Theme.secondaryColor
}
}
delegate: ListItem {
id: orderItem
contentHeight: itemColumn.height + 2 * Theme.paddingMedium
menu: ContextMenu {
MenuItem {
text: page.tr("edit_order")
onClicked: page.openOrder(index)
}
MenuItem {
text: page.tr("delete")
onClicked: page.deleteOrder(index, orderItem)
}
}
Column {
id: itemColumn
x: Theme.horizontalPageMargin
y: Theme.paddingMedium
width: parent.width - 2 * Theme.horizontalPageMargin - deliveredSwitch.width
- Theme.paddingMedium
Label {
width: parent.width
text: model.customer
truncationMode: TruncationMode.Fade
color: orderItem.highlighted ? Theme.highlightColor : Theme.primaryColor
font.pixelSize: Theme.fontSizeSmall
}
Label {
width: parent.width
truncationMode: TruncationMode.Fade
font.pixelSize: Theme.fontSizeExtraSmall
color: Theme.secondaryColor
text: page.tr("breed") + ": " + model.breed
+ " " + page.tr("eggs_req") + ": " + model.eggs
}
Label {
width: parent.width
truncationMode: TruncationMode.Fade
font.pixelSize: Theme.fontSizeExtraSmall
color: model.late ? Theme.errorColor : Theme.secondaryColor
text: page.tr("delivery") + ": " + model.delivery + " " + model.status
}
}
// interruttore "consegnato": in Silica un Switch senza testo
Switch {
id: deliveredSwitch
anchors {
right: parent.right
rightMargin: Theme.horizontalPageMargin
verticalCenter: parent.verticalCenter
}
checked: model.delivered
onClicked: page.toggleDelivered(index)
}
}
PullDownMenu {
MenuItem {
text: page.tr("new_order")
onClicked: page.openOrder(-1)
}
MenuItem {
text: page.tr("customers_list")
onClicked: pageStack.push(Qt.resolvedUrl("CustomersPage.qml"))
}
MenuItem {
text: page.tr("settings")
onClicked: pageStack.push(Qt.resolvedUrl("SettingsPage.qml"))
}
}
footer: Item {
width: listView.width
height: footerLabel.height + 3 * Theme.paddingLarge
Label {
id: footerLabel
x: Theme.horizontalPageMargin
y: Theme.paddingMedium
width: parent.width - 2 * Theme.horizontalPageMargin
visible: page.hintMsg.length > 0
text: page.hintMsg
wrapMode: Text.WordWrap
horizontalAlignment: Text.AlignHCenter
font.pixelSize: Theme.fontSizeExtraSmall
color: Theme.secondaryHighlightColor
}
}
VerticalScrollDecorator {}
}
// banner in basso: Label + Timer (in Silica non esiste un Toast)
Rectangle {
anchors {
left: parent.left
right: parent.right
bottom: parent.bottom
}
height: bannerLabel.height + 2 * Theme.paddingMedium
color: Theme.rgba(Theme.highlightBackgroundColor, 0.9)
visible: page.banner.length > 0
Label {
id: bannerLabel
x: Theme.horizontalPageMargin
y: Theme.paddingMedium
width: parent.width - 2 * Theme.horizontalPageMargin
text: page.banner
wrapMode: Text.WordWrap
horizontalAlignment: Text.AlignHCenter
font.pixelSize: Theme.fontSizeExtraSmall
color: Theme.highlightColor
}
}
Component.onCompleted: {
page.reload()
// Prima apertura: l'introduzione si presenta da sola (come l'originale).
// Differita: spingere una pagina mentre la prima si sta creando e'
// una corsa che su Qt 5.6 fa fallire l'apertura.
if (!appSettings.introSeen)
introTimer.start()
}
// Al ritorno dalle altre pagine i dati possono essere cambiati (galline,
// ordini, clienti, ripristino di un backup): la Home si ricarica.
onStatusChanged: {
if (status !== PageStatus.Active)
return
if (page.ready)
page.reload()
page.ready = true
}
Timer {
id: introTimer
interval: 100
onTriggered: pageStack.push(Qt.resolvedUrl("IntroPage.qml"), { "firstRun": true })
}
}