I built a time logger app for freelancers
- Hank
- 26 Feb, 2026
I created a new app for people who need to track exactly how much time they spend on client work. It is especially useful for freelancers who juggle multiple clients and projects and need clean records for billing.## Why I built itI wanted something focused and lightweight: start tracking quickly, organize work by client and project, and turn tracked time into invoices and reports without extra setup.## Key features### Log time by project and clientTrack time entries against specific clients and projects so every billable hour is attached to the right work.### Manage clientsCreate and manage your client list in one place, making it easier to organize ongoing and completed work.### Manage projects with hourly rateSet up projects with a name and hourly rate. This helps calculate totals accurately for invoices and reports.### Share log details via URLNeed to show work details quickly? Share time log details using a simple URL.### Generate invoices and reportsConvert tracked time into invoice-ready summaries and clear reports for clients or your own records.### Simple, fresh, responsive UIThe app is designed to stay clean and fast on both desktop and mobile.## Screenshots Dashboard view Manage project details Client management Invoice and report view Shared log detailsIf you are freelancing and want a practical way to track time, share proof of work, and bill confidently, this app is built for you.## Try it now[Open Time Logger](https://tl.momane.com)## ThanksBig thanks to Cloudflare, Neon, and Resend, their generosity and support make this happen.
Read MoreI created a new app called IronTrack
- Hank
- 13 Feb, 2026
IronTrack is my new mobile app for anyone who wants a simple, motivating way to plan gym sessions and track every workout. I built it to remove the friction between "I should train" and "I did train" by making plans easy to create and logging fast and reliable.## Why I built IronTrackI wanted one place to plan workouts, stick to them, and see progress without a dozen taps. IronTrack focuses on the essentials: smart plans, manual control, streaks, and precise tracking for every set.## Key features### AI plan generatorTell IronTrack your height, weight, age, and goal, and it builds a plan for you. This is great if you want a quick, reasonable starting point without spending hours researching routines.### Manual plan builderPrefer full control? Build a plan by hand. Add exercises, customize sets and reps, and tweak things anytime.### StreaksConsistency matters. IronTrack shows your streaks so you can keep the momentum and see your habits build over time.### Detailed workout trackingLog every workout with time, reps, weight, and exercise name. IronTrack keeps it clean and fast, so you can stay focused on training.## Screenshots Dashboard IronTrack Plans Tracking workout Exercise details Finish screen History screen AI plan builder Chinese version Workout history Exercise library## How to get started1. Create a plan with AI or build one manually. 2. Start your workout and log time, reps, and weight as you go. 3. Keep your streak alive and watch your history grow.## ShoutoutsBig thanks to Cloudflare, GitHub, and Neon for their generous free tiers. They made it much easier to ship IronTrack and keep costs low while I built and tested the app.If you want a clean, focused gym tracker that still feels smart, IronTrack is for you. I would love feedback and feature requests as I keep improving it.
Read MoreMendix: how to set entity association in Java Action
- Hank
- 08 Dec, 2024
In Mendix, you can set an entity association in a microflow by simply set the association attribute of the entity. But how to do this in a Java Action?Let's say you have two entities, `EntityA` and `EntityB`, and EntityA has a one to many association with EntityB. The association attribute in EntityB is `EntityB_EntityA`. Well its quite straight forward.Here is the code snippet:```java import com.mendix.core.Core; import com.mendix.systemwideinterfaces.core.IContext; import com.mendix.systemwideinterfaces.core.IMendixObject; import com.mendix.systemwideinterfaces.core.IMendixObjectMember; import module.name.proxies.EntityA; import module.name.proxies.EntityB;//... your other action codeEntityB entityB = new EntityB(context); EntityA entityA = new EntityA(context); entityB.setEntityB_EntityA(entityA); // when everything is done, you might also want to commit it entityB.commit() ```
Read MoreThe differences between React.memo, useCallback and useMemo
- Hank
- 03 Dec, 2024
In React, `React.memo`, `useCallback` and `useMemo` are three hooks that can help you optimize your React application. They are similar in some ways, but they are used for different purposes. In this article, I will explain the differences between them.## React.memo`React.memo` is a higher-order component that can be used to prevent unnecessary re-renders of a functional component. It is similar to `PureComponent` in class components. When you wrap a functional component with `React.memo`, React will only re-render the component if the props have changed.Here is an example:```javascript import React from 'react';const MyComponent = React.memo(({ name }) => { return {name}; }); ``` In the example above, `MyComponent` will only re-render if the `name` prop has changed.## useCallback`useCallback` is a hook that returns a memoized version of a callback function. It is useful when you need to pass a callback function to a child component, and you want to prevent the child component from re-rendering unnecessarily.Here is an example:```javascript import React, { useCallback } from 'react';const MyComponent = ({ onClick }) => { return Click me; };const ParentComponent = () => { const handleClick = useCallback(() => { console.log('Button clicked'); }, []); return ; }; ```In the example above, `MyComponent` will only re-render if the `onClick` prop has changed.## useMemo`useMemo` is a hook that returns a memoized value. It is useful when you need to calculate a value that is expensive to compute, and you want to prevent the value from being recalculated unnecessarily.Here is an example:```javascript import React, { useMemo } from 'react';const MyComponent = ({ a, b }) => { const result = useMemo(() => { return a + b; }, [a, b]); return {result}; }; ```In the example above, `result` will only be recalculated if the `a` or `b` props have changed.## Summary- `React.memo` is used to prevent unnecessary re-renders of a functional component. - `useCallback` is used to return a memoized version of a callback function. - `useMemo` is used to return a memoized value. - `useCallback` is actually a special case of `useMemo`, where the memoized value is a function.
Read MoreHow to shuffle an array
- Hank
- 03 Dec, 2024
In a small talk, one of my friends asked me, without searching or using AI, can you figure out how to shuffle an array in JavaScript? Surprisingly, I couldn't answer him immediately. I was thinking about the `sort` method, but I couldn't remember the exact implementation, and I even thought about using timestamp as seed to generate a random number.After the talk, I figured out the answer in a very short time. Here is the code:```javascriptconst shuffle = (arr) => arr.sort(() => Math.random() - 0.5); ``` Fine, this code is written by AI and its so simple and obvious. And here is the code that I want to write:```javascript const shuffle = (arr) => { const len = arr.length; for (let i = 0; i { for (let i = arr.length - 1; i > 0; i--) { const randomIdx = Math.floor(Math.random() * (i + 1)); [arr[i], arr[randomIdx]] = [arr[randomIdx], arr[i]]; } return arr; } ```why it is more effective? Because - compare with using sort function, it only needs to iterate through the array once, while the sort method may iterate multiple times - compare with my implementation, mine always generates a random number which from 0 to the length of the array, while Fisher-Yates shuffle algorithm only generates a random number which from 0 to the current index of the array. - compare with my implementation, mine always swaps the element with itself, while Fisher-Yates shuffle algorithm only swaps the element with the element that has not been swapped.-EOF-
Read More