aboutsummaryrefslogtreecommitdiff
path: root/src/common
diff options
context:
space:
mode:
authorJoris2017-09-24 22:14:48 +0200
committerJoris2017-11-07 09:33:01 +0100
commit898e7ed11ab0958fcdaf65b99b33f7b04787630a (patch)
tree8b5ab951c36d7d27550a7c4eaad16bbd2cd0edb1 /src/common
parent14bdbc8c937f5d0b35c61350dba28cb41c3737cd (diff)
Bootstrap with GHCJS and reflex:
- setup login and logout, - first draft of payment view.
Diffstat (limited to 'src/common')
-rw-r--r--src/common/Message.hs12
-rw-r--r--src/common/Message/Key.hs152
-rw-r--r--src/common/Message/Lang.hs7
-rw-r--r--src/common/Message/Translation.hs697
-rw-r--r--src/common/Model.hs40
-rw-r--r--src/common/Model/Category.hs26
-rw-r--r--src/common/Model/CreateCategory.hs16
-rw-r--r--src/common/Model/CreateIncome.hs16
-rw-r--r--src/common/Model/CreatePayment.hs23
-rw-r--r--src/common/Model/Currency.hs14
-rw-r--r--src/common/Model/EditCategory.hs19
-rw-r--r--src/common/Model/EditIncome.hs19
-rw-r--r--src/common/Model/EditPayment.hs25
-rw-r--r--src/common/Model/Frequency.hs16
-rw-r--r--src/common/Model/Income.hs29
-rw-r--r--src/common/Model/Init.hs28
-rw-r--r--src/common/Model/InitResult.hs19
-rw-r--r--src/common/Model/Payment.hs33
-rw-r--r--src/common/Model/PaymentCategory.hs27
-rw-r--r--src/common/Model/SignIn.hs16
-rw-r--r--src/common/Model/User.hs29
-rw-r--r--src/common/Util/Text.hs41
-rw-r--r--src/common/View/Format.hs69
23 files changed, 1373 insertions, 0 deletions
diff --git a/src/common/Message.hs b/src/common/Message.hs
new file mode 100644
index 0000000..9ae735d
--- /dev/null
+++ b/src/common/Message.hs
@@ -0,0 +1,12 @@
+module Common.Message
+ ( get
+ ) where
+
+import Data.Text (Text)
+
+import Common.Message.Key (Key)
+import Common.Message.Lang (Lang(..))
+import qualified Common.Message.Translation as Translation
+
+get :: Key -> Text
+get = Translation.get French
diff --git a/src/common/Message/Key.hs b/src/common/Message/Key.hs
new file mode 100644
index 0000000..4127808
--- /dev/null
+++ b/src/common/Message/Key.hs
@@ -0,0 +1,152 @@
+module Common.Message.Key
+ ( Key(..)
+ ) where
+
+import Data.Text
+
+data Key =
+
+ App_Title
+
+ | Category_Add
+ | Category_Clone
+ | Category_Color
+ | Category_DeleteConfirm
+ | Category_Edit
+ | Category_Empty
+ | Category_Name
+ | Category_NotDeleted
+ | Category_Title
+ | Category_Used
+
+ | Date_Long Int Text Int
+ | Date_Short Int Int Int
+ | Date_ShortMonthAndYear Int Int
+
+ | Dialog_Confirm
+ | Dialog_Undo
+
+ | Error_CategoryCreate
+ | Error_CategoryDelete
+ | Error_CategoryEdit
+ | Error_IncomeCreate
+ | Error_IncomeDelete
+ | Error_IncomeEdit
+ | Error_PaymentCreate
+ | Error_PaymentDelete
+ | Error_PaymentEdit
+ | Error_SignOut
+
+ | Form_AlreadyExists
+ | Form_CostMustNotBeNull
+ | Form_Empty
+ | Form_GreaterIntThan Int
+ | Form_InvalidCategory
+ | Form_InvalidColor
+ | Form_InvalidDate
+ | Form_InvalidInt
+ | Form_InvalidString
+ | Form_SmallerIntThan Int
+
+ | HttpError_BadPayload
+ | HttpError_BadUrl
+ | HttpError_NetworkError
+ | HttpError_Timeout
+
+ | Income_AddLong
+ | Income_AddShort
+ | Income_Amount
+ | Income_Clone
+ | Income_CumulativeSince Text
+ | Income_Date
+ | Income_DeleteConfirm
+ | Income_Edit
+ | Income_Empty
+ | Income_MonthlyNet
+ | Income_NotDeleted
+ | Income_Title
+
+ | Month_January
+ | Month_February
+ | Month_March
+ | Month_April
+ | Month_May
+ | Month_June
+ | Month_July
+ | Month_August
+ | Month_September
+ | Month_October
+ | Month_November
+ | Month_December
+
+ | PageNotFound_Title
+
+ | Payment_Add
+ | Payment_Balanced
+ | Payment_Category
+ | Payment_CloneLong
+ | Payment_CloneShort
+ | Payment_Cost
+ | Payment_Date
+ | Payment_Delete
+ | Payment_DeleteConfirm
+ | Payment_EditLong
+ | Payment_EditShort
+ | Payment_Empty
+ | Payment_Frequency
+ | Payment_InvalidFrequency
+ | Payment_Many
+ | Payment_MonthlyFemale
+ | Payment_MonthlyMale
+ | Payment_Name
+ | Payment_NotDeleted
+ | Payment_One
+ | Payment_PunctualFemale
+ | Payment_PunctualMale
+ | Payment_Title
+ | Payment_User
+ | Payment_Worth Text Text
+
+ | Search_Monthly
+ | Search_Name
+ | Search_Punctual
+
+ | Secure_Forbidden
+ | Secure_Unauthorized
+
+ | SignIn_Button
+ | SignIn_DisconnectSuccess
+ | SignIn_EmailInvalid
+ | SignIn_EmailPlaceholder
+ | SignIn_EmailSendFail
+ | SignIn_EmailSent
+ | SignIn_LinkExpired
+ | SignIn_LinkInvalid
+ | SignIn_LinkUsed
+ | SignIn_MailTitle
+ | SignIn_MailBody Text Text
+ | SignIn_ParseError
+
+ | Statistic_Title
+ | Statistic_ByMonthsAndMean Text
+ | Statistic_By Text Text
+ | Statistic_Total
+
+ | WeeklyReport_Empty
+ | WeeklyReport_IncomesCreated Int
+ | WeeklyReport_IncomesDeleted Int
+ | WeeklyReport_IncomesEdited Int
+ | WeeklyReport_IncomeCreated Int
+ | WeeklyReport_IncomeDeleted Int
+ | WeeklyReport_IncomeEdited Int
+ | WeeklyReport_PayedFor Text Text Text Text
+ | WeeklyReport_PayedForNot Text Text Text Text
+ | WeeklyReport_PayedFrom Text Text Text
+ | WeeklyReport_PayedFromNot Text Text Text
+ | WeeklyReport_PaymentsCreated Int
+ | WeeklyReport_PaymentsDeleted Int
+ | WeeklyReport_PaymentsEdited Int
+ | WeeklyReport_PaymentCreated Int
+ | WeeklyReport_PaymentDeleted Int
+ | WeeklyReport_PaymentEdited Int
+ | WeeklyReport_Title
diff --git a/src/common/Message/Lang.hs b/src/common/Message/Lang.hs
new file mode 100644
index 0000000..0a32ede
--- /dev/null
+++ b/src/common/Message/Lang.hs
@@ -0,0 +1,7 @@
+module Common.Message.Lang
+ ( Lang(..)
+ ) where
+
+data Lang =
+ English
+ | French
diff --git a/src/common/Message/Translation.hs b/src/common/Message/Translation.hs
new file mode 100644
index 0000000..900a9e9
--- /dev/null
+++ b/src/common/Message/Translation.hs
@@ -0,0 +1,697 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+module Common.Message.Translation
+ ( get
+ ) where
+
+import Data.Text (Text)
+import qualified Data.Text as T
+
+import Common.Message.Key
+import Common.Message.Lang (Lang(..))
+
+get :: Lang -> Key -> Text
+get = m
+
+m :: Lang -> Key -> Text
+
+m l App_Title =
+ case l of
+ English -> "Shared Cost"
+ French -> "Partage des frais"
+
+m l Category_Add =
+ case l of
+ English -> "Add an category"
+ French -> "Ajouter une catégorie"
+
+m l Category_Clone =
+ case l of
+ English -> "Clone an category"
+ French -> "Cloner une catégorie"
+
+m l Category_Color =
+ case l of
+ English -> "Color"
+ French -> "Couleur"
+
+m l Category_DeleteConfirm =
+ case l of
+ English -> "Are you sure to delete this category ?"
+ French -> "Voulez-vous vraiment supprimer cette catégorie ?"
+
+m l Category_Edit =
+ case l of
+ English -> "Edit an category"
+ French -> "Modifier une catégorie"
+
+m l Category_Empty =
+ case l of
+ English -> "No category."
+ French -> "Aucune catégorie."
+
+m l Category_Name =
+ case l of
+ English -> "Name"
+ French -> "Nom"
+
+m l Category_NotDeleted =
+ case l of
+ English -> "The category could not have been deleted."
+ French -> "La catégorie n’a pas pu être supprimé."
+
+m l Category_Title =
+ case l of
+ English -> "Categories"
+ French -> "Catégories"
+
+m l Category_Used =
+ case l of
+ English -> "This category is currently being used"
+ French -> "Cette catégorie est actuellement utilisée"
+
+m l (Date_Short day month year) =
+ case l of
+ English ->
+ T.intercalate "-" [ padded year 4, padded month 2, padded day 2 ]
+ French ->
+ T.intercalate "/" [ padded day 2, padded month 2, padded year 4 ]
+ where padded num pad =
+ let str = show num
+ in T.pack $ replicate (pad - length str) '0' ++ str
+
+m l (Date_ShortMonthAndYear month year) =
+ case l of
+ English ->
+ T.intercalate "-" . map (T.pack . show) $ [ year, month ]
+ French ->
+ T.intercalate "/" . map (T.pack . show) $ [ month, year ]
+
+m l (Date_Long day month year) =
+ case l of
+ English ->
+ T.concat [ month, " " , T.pack . show $ day, ", ", T.pack . show $ year ]
+ French ->
+ T.intercalate " " [ T.pack . show $ day, month, T.pack . show $ year ]
+
+m l Dialog_Confirm =
+ case l of
+ English -> "Confirm"
+ French -> "Confirmer"
+
+m l Dialog_Undo =
+ case l of
+ English -> "Undo"
+ French -> "Annuler"
+
+m l Error_CategoryCreate =
+ case l of
+ English -> "Error at category creation"
+ French -> "Erreur lors de la création de la catégorie"
+
+m l Error_CategoryDelete =
+ case l of
+ English -> "Error at category deletion"
+ French -> "Erreur lors de la suppression de la catégorie"
+
+m l Error_CategoryEdit =
+ case l of
+ English -> "Error at category edition"
+ French -> "Erreur lors de la modification de la catégorie"
+
+m l Error_IncomeCreate =
+ case l of
+ English -> "Error at income creation"
+ French -> "Erreur lors de la création du revenu"
+
+m l Error_IncomeDelete =
+ case l of
+ English -> "Error at income deletion"
+ French -> "Erreur lors de la suppression du revenu"
+
+m l Error_IncomeEdit =
+ case l of
+ English -> "Error at income edition"
+ French -> "Erreur lors de la modification du revenu"
+
+m l Error_PaymentCreate =
+ case l of
+ English -> "Error at payment creation"
+ French -> "Erreur lors de la création du paiement"
+
+m l Error_PaymentDelete =
+ case l of
+ English -> "Error at payment deletion"
+ French -> "Erreur lors de la suppression du paiement"
+
+m l Error_PaymentEdit =
+ case l of
+ English -> "Error at payment edition"
+ French -> "Erreur lors de la modification du paiement"
+
+m l Error_SignOut =
+ case l of
+ English -> "Error at sign out"
+ French -> "Erreur lors de la déconnexion"
+
+m l Form_AlreadyExists =
+ case l of
+ English -> "Dupplicate field"
+ French -> "Doublon"
+
+m l Form_CostMustNotBeNull =
+ case l of
+ English -> "Cost must not be zero"
+ French -> "Le coût ne doît pas être nul"
+
+m l Form_Empty =
+ case l of
+ English -> "Required field"
+ French -> "Champ requis"
+
+m l (Form_GreaterIntThan number) =
+ case l of
+ English -> T.concat [ "Integer smaller than ", T.pack . show $ number, " or equal required" ]
+ French -> T.concat [ "Entier inférieur ou égal à ", T.pack . show $ number, " requis" ]
+
+m l Form_InvalidCategory =
+ case l of
+ English -> "Invalid category"
+ French -> "Catégorie invalide"
+
+m l Form_InvalidColor =
+ case l of
+ English -> "Invalid color"
+ French -> "Couleur invalide"
+
+m l Form_InvalidDate =
+ case l of
+ English -> "day/month/year required"
+ French -> "jour/mois/année requis"
+
+m l Form_InvalidInt =
+ case l of
+ English -> "Integer required"
+ French -> "Entier requis"
+
+m l Form_InvalidString =
+ case l of
+ English -> "String required"
+ French -> "Chaîne de caractères requise"
+
+m l (Form_SmallerIntThan number) =
+ case l of
+ English -> T.concat [ "Integer bigger than ", T.pack . show $ number, " or equal required" ]
+ French -> T.concat [ "Entier supérieur ou égal à ", T.pack . show $ number, " requis" ]
+
+m l HttpError_BadPayload =
+ case l of
+ English -> "Bad payload server error"
+ French -> "Contenu inattendu en provenance du serveur"
+
+m l HttpError_BadUrl =
+ case l of
+ English -> "URL not valid"
+ French -> "l’URL n’est pas valide"
+
+m l HttpError_NetworkError =
+ case l of
+ English -> "Network can not be reached"
+ French -> "Le serveur n’est pas accessible"
+
+m l HttpError_Timeout =
+ case l of
+ English -> "Timeout server error"
+ French -> "Le serveur met trop de temps à répondre"
+
+m l Income_AddLong =
+ case l of
+ English -> "Add an income"
+ French -> "Ajouter un revenu"
+
+m l Income_AddShort =
+ case l of
+ English -> "Add"
+ French -> "Ajouter"
+
+m l Income_Amount =
+ case l of
+ English -> "Amount"
+ French -> "Montant"
+
+m l Income_Clone =
+ case l of
+ English -> "Clone an income"
+ French -> "Cloner un revenu"
+
+m l (Income_CumulativeSince since) =
+ case l of
+ English -> T.concat [ "Cumulative incomes since ", since ]
+ French -> T.concat [ "Revenus nets cumulés depuis le ", since ]
+
+m l Income_Date =
+ case l of
+ English -> "Date"
+ French -> "Date"
+
+m l Income_DeleteConfirm =
+ case l of
+ English -> "Are you sure to delete this income ?"
+ French -> "Voulez-vous vraiment supprimer ce revenu ?"
+
+m l Income_Edit =
+ case l of
+ English -> "Edit an income"
+ French -> "Modifier un revenu"
+
+m l Income_Empty =
+ case l of
+ English -> "No income."
+ French -> "Aucun revenu."
+
+m l Income_MonthlyNet =
+ case l of
+ English -> "Net monthly incomes"
+ French -> "Revenus mensuels nets"
+
+m l Income_NotDeleted =
+ case l of
+ English -> "The income could not have been deleted."
+ French -> "Le revenu n’a pas pu être supprimé."
+
+m l Income_Title =
+ case l of
+ English -> "Income"
+ French -> "Revenu"
+
+m l Month_January =
+ case l of
+ English -> "january"
+ French -> "janvier"
+
+m l Month_February =
+ case l of
+ English -> "february"
+ French -> "février"
+
+m l Month_March =
+ case l of
+ English -> "march"
+ French -> "mars"
+
+m l Month_April =
+ case l of
+ English -> "april"
+ French -> "avril"
+
+m l Month_May =
+ case l of
+ English -> "may"
+ French -> "mai"
+
+m l Month_June =
+ case l of
+ English -> "june"
+ French -> "juin"
+
+m l Month_July =
+ case l of
+ English -> "july"
+ French -> "juillet"
+
+m l Month_August =
+ case l of
+ English -> "august"
+ French -> "août"
+
+m l Month_September =
+ case l of
+ English -> "september"
+ French -> "septembre"
+
+m l Month_October =
+ case l of
+ English -> "october"
+ French -> "octobre"
+
+m l Month_November =
+ case l of
+ English -> "november"
+ French -> "novembre"
+
+m l Month_December =
+ case l of
+ English -> "december"
+ French -> "décembre"
+
+m l PageNotFound_Title =
+ case l of
+ English -> "Page not found"
+ French -> "Page introuvable"
+
+m l Payment_Add =
+ case l of
+ English -> "Add a payment"
+ French -> "Ajouter un paiement"
+
+m l Payment_Balanced =
+ case l of
+ English -> "Payments are balanced."
+ French -> "Les paiements sont équilibrés."
+
+m l Payment_Category =
+ case l of
+ English -> "Category"
+ French -> "Catégorie"
+
+m l Payment_CloneLong =
+ case l of
+ English -> "Clone a payment"
+ French -> "Cloner un paiement"
+
+m l Payment_CloneShort =
+ case l of
+ English -> "Clone"
+ French -> "Cloner"
+
+m l Payment_Cost =
+ case l of
+ English -> "Cost"
+ French -> "Coût"
+
+m l Payment_Date =
+ case l of
+ English -> "Date"
+ French -> "Date"
+
+m l Payment_Delete =
+ case l of
+ English -> "Delete"
+ French -> "Supprimer"
+
+m l Payment_DeleteConfirm =
+ case l of
+ English -> "Are you sure to delete this payment ?"
+ French -> "Voulez-vous vraiment supprimer ce paiement ?"
+
+m l Payment_EditLong =
+ case l of
+ English -> "Edit a payment"
+ French -> "Modifier un paiement"
+
+m l Payment_EditShort =
+ case l of
+ English -> "Edit"
+ French -> "Modifier"
+
+m l Payment_Empty =
+ case l of
+ English -> "No payment found from your search criteria."
+ French -> "Aucun paiement ne correspond à vos critères de recherches."
+
+m l Payment_Frequency =
+ case l of
+ English -> "Frequency"
+ French -> "Fréquence"
+
+m l Payment_InvalidFrequency =
+ case l of
+ English -> "Invalid frequency"
+ French -> "Fréquence invalide"
+
+m l Payment_Many =
+ case l of
+ English -> "payments"
+ French -> "paiements"
+
+m l Payment_MonthlyFemale =
+ case l of
+ English -> "Monthly"
+ French -> "Mensuelle"
+
+m l Payment_MonthlyMale =
+ case l of
+ English -> "Monthly"
+ French -> "Mensuel"
+
+m l Payment_Name =
+ case l of
+ English -> "Name"
+ French -> "Nom"
+
+m l Payment_NotDeleted =
+ case l of
+ English -> "The payment could not have been deleted."
+ French -> "Le paiement n’a pas pu être supprimé."
+
+m l Payment_One =
+ case l of
+ English -> "payment"
+ French -> "paiement"
+
+m l Payment_PunctualFemale =
+ case l of
+ English -> "Punctual"
+ French -> "Ponctuelle"
+
+m l Payment_PunctualMale =
+ case l of
+ English -> "Punctual"
+ French -> "Ponctuel"
+
+m l Payment_Title =
+ case l of
+ English -> "Payments"
+ French -> "Paiements"
+
+m l Payment_User =
+ case l of
+ English -> "Payer"
+ French -> "Payeur"
+
+m l (Payment_Worth subject amount) =
+ case l of
+ English -> T.concat [ subject, " worth ", amount ]
+ French -> T.concat [ subject, " comptabilisant ", amount ]
+
+m l Search_Monthly =
+ case l of
+ English -> "Monthly"
+ French -> "Mensuel"
+
+m l Search_Name =
+ case l of
+ English -> "Search"
+ French -> "Recherche"
+
+m l Search_Punctual =
+ case l of
+ English -> "Punctual"
+ French -> "Ponctuel"
+
+m l Secure_Unauthorized =
+ case l of
+ English -> "You are not authorized to sign in."
+ French -> "Tu n’es pas autorisé à te connecter."
+
+m l Secure_Forbidden =
+ case l of
+ English -> "You need to be logged in to perform this action"
+ French -> "Tu dois te connecter pour effectuer cette action"
+
+m l SignIn_Button =
+ case l of
+ English -> "Sign in"
+ French -> "Connexion"
+
+m l SignIn_DisconnectSuccess =
+ case l of
+ English -> "You have successfully disconnected"
+ French -> "Vous êtes à présent déconnecté."
+
+m l SignIn_EmailInvalid =
+ case l of
+ English -> "Your email is not valid."
+ French -> "Votre courriel n’est pas valide."
+
+m l SignIn_EmailPlaceholder =
+ case l of
+ English -> "Email"
+ French -> "Courriel"
+
+m l SignIn_EmailSendFail =
+ case l of
+ English -> "You are authorized to sign in, but we failed to send you the sign up email."
+ French -> "Tu es autorisé à te connecter, mais nous n’avons pas pu t’envoyer le courriel de connexion."
+
+m l SignIn_EmailSent =
+ case l of
+ English -> "We sent you an email with a connexion link."
+ French -> "Nous t’avons envoyé un courriel avec un lien pour te connecter."
+
+m l SignIn_LinkExpired =
+ case l of
+ English -> "The link expired, please sign in again."
+ French -> "Le lien sur lequel tu as cliqué a expiré, connecte-toi à nouveau."
+
+m l SignIn_LinkInvalid =
+ case l of
+ English -> "The link is invalid, please sign in again."
+ French -> "Le lien sur lequel tu as cliqué est invalide, connecte-toi à nouveau."
+
+m l SignIn_LinkUsed =
+ case l of
+ English -> "You already used this link, please sign in again."
+ French -> "Tu as déjà utilisé ce lien, connecte-toi à nouveau."
+
+m l SignIn_MailTitle =
+ case l of
+ English -> T.concat [ "Sign in to ", m l App_Title ]
+ French -> T.concat [ "Connexion à ", m l App_Title ]
+
+m l (SignIn_MailBody name url) =
+ T.intercalate
+ "\n"
+ ( case l of
+ English ->
+ [ T.concat [ "Hi ", name, "," ]
+ , ""
+ , T.concat
+ [ "Click to the following link in order to sign in to Shared Cost:"
+ , m l App_Title
+ , ":"
+ ]
+ , url
+ , ""
+ , "See you soon!"
+ ]
+ French ->
+ [ T.concat [ "Salut ", name, "," ]
+ , ""
+ , T.concat
+ [ "Clique sur le lien suivant pour te connecter à "
+ , m l App_Title
+ , ":"
+ ]
+ , url
+ , ""
+ , "À très vite !"
+ ]
+ )
+
+m l SignIn_ParseError =
+ case l of
+ English -> "Error while reading initial data."
+ French -> "Erreur lors de la lecture des données initiales."
+
+m l (Statistic_By key value) =
+ case l of
+ English -> T.concat [ key, ": ", value ]
+ French -> T.concat [ key, " : ", value ]
+
+m l (Statistic_ByMonthsAndMean amount) =
+ case l of
+ English ->
+ T.concat [ "Payments by category by month months (", amount, "on average)" ]
+ French ->
+ T.concat [ "Paiements par catégorie par mois (en moyenne ", amount, ")" ]
+
+m l Statistic_Title =
+ case l of
+ English -> "Statistics"
+ French -> "Statistiques"
+
+m l Statistic_Total =
+ case l of
+ English -> "Total"
+ French -> "Total"
+
+m l WeeklyReport_Empty =
+ case l of
+ English -> "No activity the previous week."
+ French -> "Pas d’activité la semaine passée."
+
+m l (WeeklyReport_IncomesCreated count) =
+ case l of
+ English -> T.concat [ T.pack . show $ count, " incomes created:" ]
+ French -> T.concat [ T.pack . show $ count, " revenus créés :" ]
+
+m l (WeeklyReport_IncomesDeleted count) =
+ case l of
+ English -> T.concat [ T.pack . show $ count, " incomes deleted:" ]
+ French -> T.concat [ T.pack . show $ count, " revenus supprimés :" ]
+
+m l (WeeklyReport_IncomesEdited count) =
+ case l of
+ English -> T.concat [ T.pack . show $ count, " incomes edited:" ]
+ French -> T.concat [ T.pack . show $ count, " revenus modifiés :" ]
+
+m l (WeeklyReport_IncomeCreated count) =
+ case l of
+ English -> T.concat [ T.pack . show $ count, " income created:" ]
+ French -> T.concat [ T.pack . show $ count, " revenu créé :" ]
+
+m l (WeeklyReport_IncomeDeleted count) =
+ case l of
+ English -> T.concat [ T.pack . show $ count, " income deleted:" ]
+ French -> T.concat [ T.pack . show $ count, " revenu supprimé :" ]
+
+m l (WeeklyReport_IncomeEdited count) =
+ case l of
+ English -> T.concat [ T.pack . show $ count, " income edited:" ]
+ French -> T.concat [ T.pack . show $ count, " revenu modifié :" ]
+
+m l (WeeklyReport_PayedFor name amount for at) =
+ case l of
+ English -> T.concat [ T.pack . show $ name, " payed ", amount, " for “", for, "” at ", at ]
+ French -> T.concat [ T.pack . show $ name, " a payé ", amount, " concernant « ", for, " » le ", at ]
+
+m l (WeeklyReport_PayedForNot name amount for at) =
+ case l of
+ English -> T.concat [ T.pack . show $ name, " didn’t pay ", amount, " for “", for, "” at ", at ]
+ French -> T.concat [ T.pack . show $ name, " n’a pas payé ", amount, " concernant « ", for, " » le ", at ]
+
+m l (WeeklyReport_PayedFrom name amount for) =
+ case l of
+ English -> T.concat [ T.pack . show $ name, " is payed ", amount, " of net monthly income from ", for ]
+ French -> T.concat [ T.pack . show $ name, " est payé ", amount, " net par mois à partir du ", for ]
+
+m l (WeeklyReport_PayedFromNot name amount for) =
+ case l of
+ English -> T.concat [ T.pack . show $ name, " isn’t payed ", amount, " of net monthly income from ", for ]
+ French -> T.concat [ T.pack . show $ name, " n’est pas payé ", amount, " net par mois à partir du ", for ]
+
+m l (WeeklyReport_PaymentsCreated count) =
+ case l of
+ English -> T.concat [ T.pack . show $ count, " payments created:" ]
+ French -> T.concat [ T.pack . show $ count, " paiements créés :" ]
+
+m l (WeeklyReport_PaymentsDeleted count) =
+ case l of
+ English -> T.concat [ T.pack . show $ count, " payments deleted:" ]
+ French -> T.concat [ T.pack . show $ count, " paiements supprimés :" ]
+
+m l (WeeklyReport_PaymentsEdited count) =
+ case l of
+ English -> T.concat [ T.pack . show $ count, " payments edited:" ]
+ French -> T.concat [ T.pack . show $ count, " paiements modifiés :" ]
+
+m l (WeeklyReport_PaymentCreated count) =
+ case l of
+ English -> T.concat [ T.pack . show $ count, " payment created:" ]
+ French -> T.concat [ T.pack . show $ count, " paiement créé :" ]
+
+m l (WeeklyReport_PaymentDeleted count) =
+ case l of
+ English -> T.concat [ T.pack . show $ count, " payment deleted:" ]
+ French -> T.concat [ T.pack . show $ count, " paiement supprimé :" ]
+
+m l (WeeklyReport_PaymentEdited count) =
+ case l of
+ English -> T.concat [ T.pack . show $ count, " payment edited:" ]
+ French -> T.concat [ T.pack . show $ count, " paiement modifié :" ]
+
+m l WeeklyReport_Title =
+ case l of
+ English -> "Weekly report"
+ French -> "Rapport hebdomadaire"
diff --git a/src/common/Model.hs b/src/common/Model.hs
new file mode 100644
index 0000000..075021f
--- /dev/null
+++ b/src/common/Model.hs
@@ -0,0 +1,40 @@
+module Common.Model
+ ( Category(..)
+ , CategoryId
+ , CreateCategory(..)
+ , CreateIncome(..)
+ , CreatePayment(..)
+ , Currency(..)
+ , EditCategory(..)
+ , EditIncome(..)
+ , EditPayment(..)
+ , Frequency(..)
+ , Income(..)
+ , IncomeId
+ , Init(..)
+ , InitResult(..)
+ , Payment(..)
+ , PaymentId
+ , PaymentCategory(..)
+ , PaymentCategoryId
+ , SignIn(..)
+ , User(..)
+ , UserId
+ ) where
+
+import Common.Model.Category (Category(..), CategoryId)
+import Common.Model.CreateCategory (CreateCategory(..))
+import Common.Model.CreateIncome (CreateIncome(..))
+import Common.Model.CreatePayment (CreatePayment(..))
+import Common.Model.Currency (Currency(..))
+import Common.Model.EditCategory (EditCategory(..))
+import Common.Model.EditIncome (EditIncome(..))
+import Common.Model.EditPayment (EditPayment(..))
+import Common.Model.Frequency (Frequency(..))
+import Common.Model.Income (Income(..), IncomeId)
+import Common.Model.Init (Init(..))
+import Common.Model.InitResult (InitResult(..))
+import Common.Model.Payment (Payment(..), PaymentId)
+import Common.Model.PaymentCategory (PaymentCategory(..), PaymentCategoryId)
+import Common.Model.SignIn (SignIn(..))
+import Common.Model.User (User(..), UserId)
diff --git a/src/common/Model/Category.hs b/src/common/Model/Category.hs
new file mode 100644
index 0000000..53a6bdb
--- /dev/null
+++ b/src/common/Model/Category.hs
@@ -0,0 +1,26 @@
+{-# LANGUAGE DeriveGeneric #-}
+
+module Common.Model.Category
+ ( CategoryId
+ , Category(..)
+ ) where
+
+import Data.Aeson (FromJSON, ToJSON)
+import Data.Int (Int64)
+import Data.Text (Text)
+import Data.Time (UTCTime)
+import GHC.Generics (Generic)
+
+type CategoryId = Int64
+
+data Category = Category
+ { _category_id :: CategoryId
+ , _category_name :: Text
+ , _category_color :: Text
+ , _category_createdAt :: UTCTime
+ , _category_editedAt :: Maybe UTCTime
+ , _category_deletedAt :: Maybe UTCTime
+ } deriving (Show, Generic)
+
+instance FromJSON Category
+instance ToJSON Category
diff --git a/src/common/Model/CreateCategory.hs b/src/common/Model/CreateCategory.hs
new file mode 100644
index 0000000..bfe24c5
--- /dev/null
+++ b/src/common/Model/CreateCategory.hs
@@ -0,0 +1,16 @@
+{-# LANGUAGE DeriveGeneric #-}
+
+module Common.Model.CreateCategory
+ ( CreateCategory(..)
+ ) where
+
+import Data.Aeson (FromJSON)
+import Data.Text (Text)
+import GHC.Generics (Generic)
+
+data CreateCategory = CreateCategory
+ { _createCategory_name :: Text
+ , _createCategory_color :: Text
+ } deriving (Show, Generic)
+
+instance FromJSON CreateCategory
diff --git a/src/common/Model/CreateIncome.hs b/src/common/Model/CreateIncome.hs
new file mode 100644
index 0000000..4ee3a50
--- /dev/null
+++ b/src/common/Model/CreateIncome.hs
@@ -0,0 +1,16 @@
+{-# LANGUAGE DeriveGeneric #-}
+
+module Common.Model.CreateIncome
+ ( CreateIncome(..)
+ ) where
+
+import Data.Aeson (FromJSON)
+import Data.Time.Calendar (Day)
+import GHC.Generics (Generic)
+
+data CreateIncome = CreateIncome
+ { _createIncome_date :: Day
+ , _createIncome_amount :: Int
+ } deriving (Show, Generic)
+
+instance FromJSON CreateIncome
diff --git a/src/common/Model/CreatePayment.hs b/src/common/Model/CreatePayment.hs
new file mode 100644
index 0000000..b5b6256
--- /dev/null
+++ b/src/common/Model/CreatePayment.hs
@@ -0,0 +1,23 @@
+{-# LANGUAGE DeriveGeneric #-}
+
+module Common.Model.CreatePayment
+ ( CreatePayment(..)
+ ) where
+
+import Data.Aeson (FromJSON)
+import Data.Text (Text)
+import Data.Time.Calendar (Day)
+import GHC.Generics (Generic)
+
+import Common.Model.Category (CategoryId)
+import Common.Model.Frequency (Frequency)
+
+data CreatePayment = CreatePayment
+ { _createPayment_name :: Text
+ , _createPayment_cost :: Int
+ , _createPayment_date :: Day
+ , _createPayment_category :: CategoryId
+ , _createPayment_frequency :: Frequency
+ } deriving (Show, Generic)
+
+instance FromJSON CreatePayment
diff --git a/src/common/Model/Currency.hs b/src/common/Model/Currency.hs
new file mode 100644
index 0000000..7c12545
--- /dev/null
+++ b/src/common/Model/Currency.hs
@@ -0,0 +1,14 @@
+{-# LANGUAGE DeriveGeneric #-}
+
+module Common.Model.Currency
+ ( Currency(..)
+ ) where
+
+import Data.Aeson (FromJSON, ToJSON)
+import Data.Text (Text)
+import GHC.Generics (Generic)
+
+newtype Currency = Currency Text deriving (Show, Generic)
+
+instance FromJSON Currency
+instance ToJSON Currency
diff --git a/src/common/Model/EditCategory.hs b/src/common/Model/EditCategory.hs
new file mode 100644
index 0000000..2a3a697
--- /dev/null
+++ b/src/common/Model/EditCategory.hs
@@ -0,0 +1,19 @@
+{-# LANGUAGE DeriveGeneric #-}
+
+module Common.Model.EditCategory
+ ( EditCategory(..)
+ ) where
+
+import Data.Aeson (FromJSON)
+import Data.Text (Text)
+import GHC.Generics (Generic)
+
+import Common.Model.Category (CategoryId)
+
+data EditCategory = EditCategory
+ { _editCategory_id :: CategoryId
+ , _editCategory_name :: Text
+ , _editCategory_color :: Text
+ } deriving (Show, Generic)
+
+instance FromJSON EditCategory
diff --git a/src/common/Model/EditIncome.hs b/src/common/Model/EditIncome.hs
new file mode 100644
index 0000000..a55c39e
--- /dev/null
+++ b/src/common/Model/EditIncome.hs
@@ -0,0 +1,19 @@
+{-# LANGUAGE DeriveGeneric #-}
+
+module Common.Model.EditIncome
+ ( EditIncome(..)
+ ) where
+
+import Data.Aeson (FromJSON)
+import Data.Time.Calendar (Day)
+import GHC.Generics (Generic)
+
+import Common.Model.Income (IncomeId)
+
+data EditIncome = EditIncome
+ { _editIncome_id :: IncomeId
+ , _editIncome_date :: Day
+ , _editIncome_amount :: Int
+ } deriving (Show, Generic)
+
+instance FromJSON EditIncome
diff --git a/src/common/Model/EditPayment.hs b/src/common/Model/EditPayment.hs
new file mode 100644
index 0000000..172c0c1
--- /dev/null
+++ b/src/common/Model/EditPayment.hs
@@ -0,0 +1,25 @@
+{-# LANGUAGE DeriveGeneric #-}
+
+module Common.Model.EditPayment
+ ( EditPayment(..)
+ ) where
+
+import Data.Aeson (FromJSON)
+import Data.Text (Text)
+import Data.Time.Calendar (Day)
+import GHC.Generics (Generic)
+
+import Common.Model.Category (CategoryId)
+import Common.Model.Frequency (Frequency)
+import Common.Model.Payment (PaymentId)
+
+data EditPayment = EditPayment
+ { _editPayment_id :: PaymentId
+ , _editPayment_name :: Text
+ , _editPayment_cost :: Int
+ , _editPayment_date :: Day
+ , _editPayment_category :: CategoryId
+ , _editPayment_frequency :: Frequency
+ } deriving (Show, Generic)
+
+instance FromJSON EditPayment
diff --git a/src/common/Model/Frequency.hs b/src/common/Model/Frequency.hs
new file mode 100644
index 0000000..7c46605
--- /dev/null
+++ b/src/common/Model/Frequency.hs
@@ -0,0 +1,16 @@
+{-# LANGUAGE DeriveGeneric #-}
+
+module Common.Model.Frequency
+ ( Frequency(..)
+ ) where
+
+import Data.Aeson (FromJSON, ToJSON)
+import GHC.Generics (Generic)
+
+data Frequency =
+ Punctual
+ | Monthly
+ deriving (Eq, Read, Show, Generic)
+
+instance FromJSON Frequency
+instance ToJSON Frequency
diff --git a/src/common/Model/Income.hs b/src/common/Model/Income.hs
new file mode 100644
index 0000000..280812f
--- /dev/null
+++ b/src/common/Model/Income.hs
@@ -0,0 +1,29 @@
+{-# LANGUAGE DeriveGeneric #-}
+
+module Common.Model.Income
+ ( IncomeId
+ , Income(..)
+ ) where
+
+import Data.Aeson (FromJSON, ToJSON)
+import Data.Int (Int64)
+import Data.Time (UTCTime)
+import Data.Time.Calendar (Day)
+import GHC.Generics (Generic)
+
+import Common.Model.User (UserId)
+
+type IncomeId = Int64
+
+data Income = Income
+ { _income_id :: IncomeId
+ , _income_userId :: UserId
+ , _income_date :: Day
+ , _income_amount :: Int
+ , _income_createdAt :: UTCTime
+ , _income_editedAt :: Maybe UTCTime
+ , _income_deletedAt :: Maybe UTCTime
+ } deriving (Show, Generic)
+
+instance FromJSON Income
+instance ToJSON Income
diff --git a/src/common/Model/Init.hs b/src/common/Model/Init.hs
new file mode 100644
index 0000000..68fcfb8
--- /dev/null
+++ b/src/common/Model/Init.hs
@@ -0,0 +1,28 @@
+{-# LANGUAGE DeriveGeneric #-}
+
+module Common.Model.Init
+ ( Init(..)
+ ) where
+
+import Data.Aeson (FromJSON, ToJSON)
+import GHC.Generics (Generic)
+
+import Common.Model.Category (Category)
+import Common.Model.Currency (Currency)
+import Common.Model.Income (Income)
+import Common.Model.Payment (Payment)
+import Common.Model.PaymentCategory (PaymentCategory)
+import Common.Model.User (UserId, User)
+
+data Init = Init
+ { _init_users :: [User]
+ , _init_currentUser :: UserId
+ , _init_payments :: [Payment]
+ , _init_incomes :: [Income]
+ , _init_categories :: [Category]
+ , _init_paymentCategories :: [PaymentCategory]
+ , _init_currency :: Currency
+ } deriving (Show, Generic)
+
+instance FromJSON Init
+instance ToJSON Init
diff --git a/src/common/Model/InitResult.hs b/src/common/Model/InitResult.hs
new file mode 100644
index 0000000..43c16f9
--- /dev/null
+++ b/src/common/Model/InitResult.hs
@@ -0,0 +1,19 @@
+{-# LANGUAGE DeriveGeneric #-}
+
+module Common.Model.InitResult
+ ( InitResult(..)
+ ) where
+
+import Data.Aeson (FromJSON, ToJSON)
+import Data.Text (Text)
+import GHC.Generics (Generic)
+
+import Common.Model.Init (Init)
+
+data InitResult =
+ InitSuccess Init
+ | InitEmpty (Either Text (Maybe Text))
+ deriving (Show, Generic)
+
+instance FromJSON InitResult
+instance ToJSON InitResult
diff --git a/src/common/Model/Payment.hs b/src/common/Model/Payment.hs
new file mode 100644
index 0000000..804b501
--- /dev/null
+++ b/src/common/Model/Payment.hs
@@ -0,0 +1,33 @@
+{-# LANGUAGE DeriveGeneric #-}
+
+module Common.Model.Payment
+ ( PaymentId
+ , Payment(..)
+ ) where
+
+import Data.Aeson (FromJSON, ToJSON)
+import Data.Int (Int64)
+import Data.Text (Text)
+import Data.Time (UTCTime)
+import Data.Time.Calendar (Day)
+import GHC.Generics (Generic)
+
+import Common.Model.Frequency
+import Common.Model.User (UserId)
+
+type PaymentId = Int64
+
+data Payment = Payment
+ { _payment_id :: PaymentId
+ , _payment_user :: UserId
+ , _payment_name :: Text
+ , _payment_cost :: Int
+ , _payment_date :: Day
+ , _payment_frequency :: Frequency
+ , _payment_createdAt :: UTCTime
+ , _payment_editedAt :: Maybe UTCTime
+ , _payment_deletedAt :: Maybe UTCTime
+ } deriving (Show, Generic)
+
+instance FromJSON Payment
+instance ToJSON Payment
diff --git a/src/common/Model/PaymentCategory.hs b/src/common/Model/PaymentCategory.hs
new file mode 100644
index 0000000..a0e94f9
--- /dev/null
+++ b/src/common/Model/PaymentCategory.hs
@@ -0,0 +1,27 @@
+{-# LANGUAGE DeriveGeneric #-}
+
+module Common.Model.PaymentCategory
+ ( PaymentCategoryId
+ , PaymentCategory(..)
+ ) where
+
+import Data.Aeson (FromJSON, ToJSON)
+import Data.Int (Int64)
+import Data.Text (Text)
+import Data.Time (UTCTime)
+import GHC.Generics (Generic)
+
+import Common.Model.Category (CategoryId)
+
+type PaymentCategoryId = Int64
+
+data PaymentCategory = PaymentCategory
+ { _paymentCategory_id :: PaymentCategoryId
+ , _paymentCategory_name :: Text
+ , _paymentCategory_category :: CategoryId
+ , _paymentCategory_createdAt :: UTCTime
+ , _paymentCategory_editedAt :: Maybe UTCTime
+ } deriving (Show, Generic)
+
+instance FromJSON PaymentCategory
+instance ToJSON PaymentCategory
diff --git a/src/common/Model/SignIn.hs b/src/common/Model/SignIn.hs
new file mode 100644
index 0000000..f4da97f
--- /dev/null
+++ b/src/common/Model/SignIn.hs
@@ -0,0 +1,16 @@
+{-# LANGUAGE DeriveGeneric #-}
+
+module Common.Model.SignIn
+ ( SignIn(..)
+ ) where
+
+import Data.Aeson (FromJSON, ToJSON)
+import Data.Text (Text)
+import GHC.Generics (Generic)
+
+data SignIn = SignIn
+ { _signIn_email :: Text
+ } deriving (Show, Generic)
+
+instance FromJSON SignIn
+instance ToJSON SignIn
diff --git a/src/common/Model/User.hs b/src/common/Model/User.hs
new file mode 100644
index 0000000..8c64bc2
--- /dev/null
+++ b/src/common/Model/User.hs
@@ -0,0 +1,29 @@
+{-# LANGUAGE DeriveGeneric #-}
+
+module Common.Model.User
+ ( UserId
+ , User(..)
+ , find
+ ) where
+
+import Data.Aeson (FromJSON, ToJSON)
+import qualified Data.List as L
+import Data.Int (Int64)
+import Data.Text (Text)
+import Data.Time (UTCTime)
+import GHC.Generics (Generic)
+
+type UserId = Int64
+
+data User = User
+ { _user_id :: UserId
+ , _user_creation :: UTCTime
+ , _user_email :: Text
+ , _user_name :: Text
+ } deriving (Show, Generic)
+
+instance FromJSON User
+instance ToJSON User
+
+find :: UserId -> [User] -> Maybe User
+find userId users = L.find ((== userId) . _user_id) users
diff --git a/src/common/Util/Text.hs b/src/common/Util/Text.hs
new file mode 100644
index 0000000..4af7a4c
--- /dev/null
+++ b/src/common/Util/Text.hs
@@ -0,0 +1,41 @@
+module Common.Util.Text
+ ( unaccent
+ ) where
+
+import Data.Text (Text)
+import qualified Data.Text as T
+
+unaccent :: Text -> Text
+unaccent = T.map unaccentChar
+
+unaccentChar :: Char -> Char
+unaccentChar c = case c of
+ 'à' -> 'a'
+ 'á' -> 'a'
+ 'â' -> 'a'
+ 'ã' -> 'a'
+ 'ä' -> 'a'
+ 'ç' -> 'c'
+ 'è' -> 'e'
+ 'é' -> 'e'
+ 'ê' -> 'e'
+ 'ë' -> 'e'
+ 'ì' -> 'i'
+ 'í' -> 'i'
+ 'î' -> 'i'
+ 'ï' -> 'i'
+ 'ñ' -> 'n'
+ 'ò' -> 'o'
+ 'ó' -> 'o'
+ 'ô' -> 'o'
+ 'õ' -> 'o'
+ 'ö' -> 'o'
+ 'š' -> 's'
+ 'ù' -> 'u'
+ 'ú' -> 'u'
+ 'û' -> 'u'
+ 'ü' -> 'u'
+ 'ý' -> 'y'
+ 'ÿ' -> 'y'
+ 'ž' -> 'z'
+ _ -> c
diff --git a/src/common/View/Format.hs b/src/common/View/Format.hs
new file mode 100644
index 0000000..a7fa4e3
--- /dev/null
+++ b/src/common/View/Format.hs
@@ -0,0 +1,69 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+module Common.View.Format
+ ( shortDay
+ , longDay
+ , price
+ , number
+ ) where
+
+import Data.Text (Text)
+import qualified Data.Text as T
+import Data.List (intersperse)
+import Data.Maybe (fromMaybe)
+import Data.Time.Calendar (Day, toGregorian)
+
+import qualified Common.Message as Message
+import qualified Common.Message.Key as Key
+import Common.Model.Currency (Currency(..))
+
+shortDay :: Day -> Text
+shortDay date =
+ Message.get $ Key.Date_Short
+ day
+ month
+ (fromIntegral year)
+ where (year, month, day) = toGregorian date
+
+longDay :: Day -> Text
+longDay date =
+ Message.get $ Key.Date_Long
+ day
+ (fromMaybe "−" . fmap Message.get . monthToKey $ month)
+ (fromIntegral year)
+ where (year, month, day) = toGregorian date
+
+ monthToKey 1 = Just Key.Month_January
+ monthToKey 2 = Just Key.Month_February
+ monthToKey 3 = Just Key.Month_March
+ monthToKey 4 = Just Key.Month_April
+ monthToKey 5 = Just Key.Month_May
+ monthToKey 6 = Just Key.Month_June
+ monthToKey 7 = Just Key.Month_July
+ monthToKey 8 = Just Key.Month_August
+ monthToKey 9 = Just Key.Month_September
+ monthToKey 10 = Just Key.Month_October
+ monthToKey 11 = Just Key.Month_November
+ monthToKey 12 = Just Key.Month_December
+ monthToKey _ = Nothing
+
+price :: Currency -> Int -> Text
+price (Currency currency) amount = T.concat [ number amount, " ", currency ]
+
+number :: Int -> Text
+number n =
+ T.pack
+ . (++) (if n < 0 then "-" else "")
+ . reverse
+ . concat
+ . intersperse " "
+ . group 3
+ . reverse
+ . show
+ . abs $ n
+
+group :: Int -> [a] -> [[a]]
+group n xs =
+ if length xs <= n
+ then [xs]
+ else (take n xs) : (group n (drop n xs))